diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
index 8bd1ab0..4ca6e95 100644
--- a/.github/workflows/verify.yml
+++ b/.github/workflows/verify.yml
@@ -7,6 +7,7 @@ on:
branches:
- main
- "agent/**"
+ - "docs/**"
permissions:
contents: read
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 140c943..96d5be1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,9 +3,21 @@
All material public changes will be recorded here. Published releases are
append-only; corrections create a successor release.
+## Unreleased
+
+- Improved the public record's short-form orientation and claim traceability.
+- Added a repository social-preview asset with Ata Nasseri's accountable role.
+- Added the AEAS visual system, four claim-bounded explanatory diagrams,
+ audience-specific reading paths, and text fallbacks.
+- Added machine-readable visual metadata plus SVG, PNG, accessibility, source,
+ claim, non-claim, and Markdown-image verification.
+- Strengthened the independent technical review scope, object identity,
+ evidence-handling requirements, and statement template.
+- Extended CI verification to documentation branches.
+- Left Release `v1.0.0`, its immutable assets, and its DOI unchanged.
+
## 1.0.0 - 2026-08-19
- Prepared the initial public technical system record.
- Added the publication boundary, evidence index, assurance method, release
controls, DOI guidance, and independent-review package.
-
diff --git a/PUBLICATION_GATE.md b/PUBLICATION_GATE.md
index 3939d6c..a7e82c4 100644
--- a/PUBLICATION_GATE.md
+++ b/PUBLICATION_GATE.md
@@ -1,5 +1,7 @@
# Publication Gate
+
+
> **Repository decision:** AUTHORIZED FOR PUBLIC REPOSITORY DEPLOYMENT
>
> **Release decision:** APPROVED FOR PUBLICATION OF `v1.0.0`
diff --git a/README.md b/README.md
index 9ccc3a5..de7f03c 100644
--- a/README.md
+++ b/README.md
@@ -2,19 +2,55 @@
[](https://doi.org/10.5281/zenodo.22019207)
-
+
> **Governance, Auditability, and Release Integrity for AI-Assisted Software Engineering**
-The Agentic Engineering Assurance System is a governed approach to long-running,
-AI-assisted software delivery. It separates implementation, technical review,
-human authority, evidence, and release so that important decisions remain
-traceable after the conversation that produced them has ended.
+The Agentic Engineering Assurance System is a governed system for long-running,
+AI-assisted software delivery. It keeps implementation, read-only technical
+audit, human authority, evidence, and release distinct so that material
+decisions remain attributable and reviewable after the conversation that
+produced them has ended.
-This repository is the public technical system record. It explains the design,
-the implemented control model, the recorded outcomes, and the known limits in a
-form that can be reviewed without exposing the private operating environment or
-its sealed evidence archive.
+This repository is the bounded public technical record. It documents the
+design, implemented control model, recorded outcomes, release evidence, and
+known limitations without disclosing the private operating environment or its
+sealed evidence archive.
+
+## In 30 seconds
+
+| Question | Short answer |
+| --- | --- |
+| **What is this?** | A governed system for long-running, AI-assisted software delivery, documented as a public technical record. |
+| **What does it separate?** | Human authority, implementation, read-only technical audit, durable evidence, and release. |
+| **How is it controlled?** | Bounded work packages, pinned source, explicit finding disposition, owner gates, and a separate release step. |
+| **What can a reader inspect?** | [Public claims and their boundaries](docs/04-public-evidence-index.md), [Release v1.0.0](https://github.com/Atanasseri/agentic-engineering-assurance-system/releases/tag/v1.0.0), [publication metadata](evidence/publication.json), and the [version DOI](https://doi.org/10.5281/zenodo.22019208). |
+| **Who is accountable?** | **Ata Nasseri, System Designer and Assurance Owner.** |
+| **What is the current boundary?** | Evidence-backed and publicly released; independent technical review has not yet been performed, and no external certification is claimed. |
+
+## Choose your reading path
+
+| If you are… | Start here | Then inspect | Primary question answered |
+| --- | --- | --- | --- |
+| A product or system leader | This README and the [System Overview](docs/01-system-overview.md) | [Limitations and Reassessment](docs/05-limitations-and-reassessment.md) | What is governed, who decides, and where are the boundaries? |
+| A technical reviewer | [Implementation Record](docs/02-implementation-record.md) | [Assurance Method](docs/03-assurance-method.md) and [Public Evidence Index](docs/04-public-evidence-index.md) | What was recorded, how was it reviewed, and how strong is each claim? |
+| A risk, governance, or assurance reader | [Assurance Method](docs/03-assurance-method.md) | [Public Evidence Index](docs/04-public-evidence-index.md) and [Limitations](docs/05-limitations-and-reassessment.md) | Which decisions remain human, and what is explicitly not established? |
+| A release or provenance specialist | [Current assurance status](#current-assurance-status) | [Signing and Release](release/SIGNING_AND_RELEASE.md), [publication metadata](evidence/publication.json), and [DOI guidance](release/DOI_AND_ARCHIVAL.md) | Which exact public object is citable, persistent, and integrity-bound? |
+| A prospective independent reviewer | [Independent Review Brief](review/REVIEW_BRIEF.md) | [Review Checklist](review/REVIEW_CHECKLIST.md), [review status](docs/06-independent-review.md), and the immutable [`v1.0.0` GitHub Release](https://github.com/Atanasseri/agentic-engineering-assurance-system/releases/tag/v1.0.0) with its signed Git objects | What must be independently checked before any external conclusion is published? |
+
+## Professional accountability
+
+**Ata Nasseri, System Designer and Assurance Owner**
+
+The accountable scope represented here includes translating objectives and
+constraints into bounded acceptance criteria; defining authority boundaries;
+authorizing audit rounds; deciding criteria changes and residual risk; and
+governing release. AI implementation and audit roles operated within those
+human-defined boundaries.
+
+This describes accountable system and assurance leadership. It does not claim
+sole authorship of every implementation artifact or independent certification
+of one's own work.
## Why it exists
@@ -34,23 +70,24 @@ records, evidence-qualified claims, and controlled release.
## System at a glance
-```mermaid
-flowchart TD
- O["Owner authority"] --> W["Bounded work package"]
- W --> I["Implementation"]
- I --> P["Pinned source and handoff"]
- P --> A["Read-only technical audit"]
- A --> D["Resolution and owner gate"]
- D --> R["Controlled release"]
-```
+
+
+[Open the full-size system map](assets/visuals/system-map.svg).
The owner defines objectives, acceptance criteria, continuation authority,
residual-risk decisions, and release approval. The implementation role changes
the system. The audit role inspects a pinned source position without modifying
-it. Git provides the durable evidence surface connecting all three.
+it. Git provides the durable evidence surface connecting all three. In text,
+the governed sequence is: bounded work package → implementation → exact source
+checkpoint and integrity-bound handoff → read-only audit → finding disposition
+→ owner decision → terminal approval → separate release action.
## Recorded outcomes
+
+
+[Open the full-size recorded-outcomes visual](assets/visuals/recorded-outcomes.svg).
+
The sealed private evidence baseline supports the following public statements:
- seven audit reports were preserved across three governed reviews;
@@ -59,6 +96,8 @@ The sealed private evidence baseline supports the following public statements:
with no CRITICAL finding recorded;
- resolution records mark 32 findings as fixed and one as settled through an
owner-ratified criteria revision;
+- each of the six substantive audit reports recorded a changes-required
+ verdict;
- the final delivery review used two authorized rounds and reported eleven
MEDIUM findings;
- three final-round findings were defects introduced by earlier corrective
@@ -97,6 +136,8 @@ constitute external certification.
| [Independent Review](docs/06-independent-review.md) | Scope and status of third-party review |
| [References](docs/07-references.md) | External standards and platform mechanisms used by the public release process |
| [Publication Charter](PUBLICATION_CHARTER.md) | Public/private disclosure boundary |
+| [Visual System](assets/VISUAL_SYSTEM.md) | Semantic color, typography, connector, accessibility, and change-control rules for public visuals |
+| [Visual Manifest](assets/visuals/manifest.json) | Machine-readable claims, sources, dimensions, non-claims, and approval status for every visual asset |
Release and external-review materials are kept separate from the system
description:
@@ -104,12 +145,31 @@ description:
| Package | Purpose |
| --- | --- |
| [Publication Gate](PUBLICATION_GATE.md) | Separate repository-deployment, Release, archival, and review gates |
-| [Signing and Release](release/SIGNING_AND_RELEASE.md) | Signed commit, annotated tag, immutable release, and provenance process |
+| [Signing and Release](release/SIGNING_AND_RELEASE.md) | Signed release commit, signed annotated tag, immutable GitHub Release, and provenance process |
| [DOI and Archival](release/DOI_AND_ARCHIVAL.md) | Zenodo and persistent citation process |
| [Independent Review Brief](review/REVIEW_BRIEF.md) | Scope for a qualified external reviewer |
-| [Assurance Statement Template](review/ASSURANCE_STATEMENT_TEMPLATE.md) | Required structure of the signed external conclusion |
+| [Independent Review Checklist](review/REVIEW_CHECKLIST.md) | Evidence, identity, method, and conclusion checks for the reviewer |
+| [Independent Technical Review Statement Template](review/ASSURANCE_STATEMENT_TEMPLATE.md) | Required structure of the signed external conclusion |
+
+## Evidence and release chain
+
+
+
+[Open the full-size evidence-and-release visual](assets/visuals/evidence-release-chain.svg).
+
+The visual deliberately shows three separate groups rather than one unbroken
+proof chain:
+
+1. a public claim is bounded by its identifier, evidence class, source, and
+ explicit non-claims;
+2. the later publication record identifies the signed release commit and
+ signed annotated tag for `v1.0.0`, the immutable GitHub Release, checksums
+ and provenance workflow, and persistent version DOI without changing those
+ released objects; and
+3. independent scrutiny remains `NOT_PERFORMED` until a qualified reviewer
+ publishes a scoped conclusion under their own control.
-## Evidence relationship
+## Private evidence relationship
This public record derives from a sealed private baseline identified as
`PTE-2026-08-19-v1.0.1`. The baseline manifest is committed here only by its
@@ -123,21 +183,21 @@ The digest is a commitment to a specific private manifest. It does not reveal
the manifest, independently validate its contents, or grant public access to
the underlying evidence.
-## Professional accountability
-
-**Ata Nasseri — System Designer and Assurance Owner**
-
-Accountability included objectives, constraints, acceptance criteria,
-authority boundaries, audit authorization, owner decisions, residual-risk
-acceptance, and release governance. AI implementation and audit roles operated
-within those human-defined boundaries.
-
-## Assurance statement
-
-This repository is evidence-backed but is not yet externally certified. The
-independent-review package is prepared so that a qualified reviewer can assess
-a precise signed release and publish a scoped assurance statement without
-overstating what was examined.
+## Current assurance status
+
+Release `v1.0.0` has verified signed Git objects and an immutable GitHub
+Release. Its release assets have SHA-256 checksums and GitHub artifact
+attestations; Zenodo provides a separate persistent version DOI. Independent
+technical review has not yet been performed, and no external certification is
+claimed. The [independent-review package](review/REVIEW_BRIEF.md) is prepared
+so that a qualified reviewer can assess that precise release commit, signed
+annotated tag, and immutable GitHub Release, then publish a scoped conclusion
+tied to them.
+
+The immutable `v1.0.0` GitHub Release remains the citable release object, bound
+to a signed release commit and signed annotated tag. The default branch
+contains later publication-record and documentation refinements; they do not
+alter those signed Git objects, the Release assets, or the DOI.
## Use and citation
diff --git a/assets/README.md b/assets/README.md
index 11359bb..6381161 100644
--- a/assets/README.md
+++ b/assets/README.md
@@ -1,16 +1,38 @@
# Visual Assets
-This directory is reserved for the approved public visual system.
+This directory contains the repository's public visual assets.
-The planned visual set is:
+## Repository social preview
-1. system context and responsibility boundary;
-2. work-to-release evidence chain;
-3. audit state and owner gates;
-4. trust and assurance boundaries; and
-5. outcomes and engineering lessons.
+[`visuals/social-preview.png`](visuals/social-preview.png) is the primary
+1280×640 social preview for the repository. It presents the system's governance,
+audit, evidence, and release flow as a conceptual visual; it is not evidence and
+does not reconstruct the private operating topology.
-Markdown diagrams in the current documents are structural working visuals. The
-final branded assets will be produced only after the written system record is
-approved, so design does not outrun evidence.
+The composition identifies the accountable professional role used throughout
+the public record:
+> **Ata Nasseri, System Designer and Assurance Owner**
+
+To activate it on GitHub, upload the PNG under **Settings → General → Social
+preview** after the signed documentation change is merged.
+
+## Implemented explanatory visuals
+
+| Asset | Purpose | Primary placement |
+| --- | --- | --- |
+| [`visuals/system-map.svg`](visuals/system-map.svg) | Governed work-to-release lifecycle and durable-record spine | Repository README |
+| [`visuals/recorded-outcomes.svg`](visuals/recorded-outcomes.svg) | Bounded recorded outcomes with evidence-class labels and visible non-claims | Repository README |
+| [`visuals/evidence-release-chain.svg`](visuals/evidence-release-chain.svg) | Separation of claim discipline, the later public release record, and independent scrutiny | Repository README and Assurance Method |
+| [`visuals/authority-matrix.svg`](visuals/authority-matrix.svg) | Decision rights, role limits, and durable controls | System Overview |
+
+The [AEAS Visual System](VISUAL_SYSTEM.md) defines the semantic palette,
+typography, connector grammar, accessibility contract, and change controls.
+The machine-readable [`visuals/manifest.json`](visuals/manifest.json) registers
+every PNG and SVG with its dimensions, claim identifiers, sources, explicit
+non-claims, independent-review status, and owner-approval status.
+
+Each explanatory visual has adjacent Markdown text or a table as a fallback.
+New visuals enter the manifest as `DRAFT`. Only the Assurance Owner may change
+their status to `OWNER_APPROVED` after reviewing the exact renders in an
+authorized change.
diff --git a/assets/VISUAL_SYSTEM.md b/assets/VISUAL_SYSTEM.md
new file mode 100644
index 0000000..42c2fac
--- /dev/null
+++ b/assets/VISUAL_SYSTEM.md
@@ -0,0 +1,175 @@
+# AEAS Visual System
+
+## Purpose
+
+The AEAS visual system makes the public technical record faster to understand
+without making its claims broader, stronger, or less qualified. Visuals are
+editorial explanations of approved public content. They are not evidence,
+runtime topology, external certification, or a substitute for the claim
+register.
+
+The visual language is designed around four ideas:
+
+1. human authority remains visible and attributable;
+2. implementation, audit, evidence, and release remain distinct;
+3. the main reading path is clear before supporting detail is introduced; and
+4. limitations remain visible wherever a visual could otherwise overstate the
+ record.
+
+## Design principles
+
+### Meaning before decoration
+
+Every node, connector, number, color, and annotation must explain a supported
+relationship. Decorative infrastructure, invented topology, generic AI
+imagery, and unreferenced metrics are excluded from explanatory diagrams.
+
+### One focal decision
+
+Each visual should use the authority accent for no more than one or two focal
+elements. Color identifies semantic role; it is never applied merely to make
+peer elements look different.
+
+### Static first
+
+Repository visuals are static, self-contained SVG files with a complete first
+frame. They use no script, remote font, external stylesheet, embedded raster
+image, or network dependency. Motion belongs only on the separate AEAS website
+and must preserve an equivalent static state.
+
+### Claim-governed visuals
+
+Every public visual is registered in
+[`visuals/manifest.json`](visuals/manifest.json) with:
+
+- a stable visual identifier;
+- its public claim identifiers;
+- the public documents from which it is derived;
+- concise non-claims;
+- accessible alternative text;
+- its approval status; and
+- its intrinsic dimensions and view box.
+
+Visual metadata records provenance and review boundaries. It does not turn an
+illustration into evidence.
+
+## Semantic color roles
+
+The palette aligns with the existing AEAS social identity while using a quieter
+editorial treatment for technical reading.
+
+| Role | Light surface | Light ink/stroke | Dark surface | Dark ink/stroke | Meaning |
+| --- | --- | --- | --- | --- | --- |
+| Paper | `#F6F8FB` | `#101828` | `#07111F` | `#F2F4F7` | Canvas and primary text |
+| Raised paper | `#FFFFFF` | `#344054` | `#0D1A2B` | `#D0D5DD` | Bounded nodes and tables |
+| Muted | `#EEF1F5` | `#5F6B7A` | `#162337` | `#AEBAC9` | Supporting context |
+| Structure | transparent | `#475467` | transparent | `#98A2B3` | Neutral implementation flow |
+| Human authority | `#FFF3D6` | `#9A5B00` | `#2B2110` | `#F2B84B` | Owner decisions and authority gates |
+| Audit | `#E7F8FB` | `#08798B` | `#0B2831` | `#45D3E8` | Read-only technical audit |
+| Evidence | `#EAF1F8` | `#264B6B` | `#10283D` | `#88B8E0` | Durable records and evidence classes |
+| Release | `#EDF1FF` | `#3548B5` | `#171F46` | `#9BACFF` | Release identity and archival path |
+| Limitation | `#FFF0EE` | `#B42318` | `#351818` | `#FF8A80` | Qualification, exclusion, or unresolved boundary |
+
+Color must always be paired with visible text, a line style, or a role label so
+that meaning does not depend on color perception.
+
+## Typography
+
+- Primary family: `Segoe UI`, `Inter`, `Arial`, sans-serif.
+- Technical labels: `SFMono-Regular`, `Consolas`, `Liberation Mono`, monospace.
+- Maximum weights: 400, 600, and 700 for large numeric outcomes only.
+- Sentence case is used for titles and labels.
+- Acronyms and status vocabulary retain their canonical uppercase form.
+- Text must remain readable when a wide visual is displayed at 720 CSS pixels.
+
+No remote font is required. This removes a network dependency and keeps each
+SVG self-contained.
+
+## Geometry
+
+- Base spacing unit: 8 pixels.
+- Standard gaps: 16, 24, 32, 48, and 64 pixels.
+- Standard node radius: 12 pixels.
+- Structural border: 1.5 pixels.
+- Primary connector: 2 pixels, solid.
+- Evidence or reporting connector: 2 pixels, dashed.
+- Maximum target density: approximately four out of ten.
+- The normal repository view box is 960 units wide with height determined by
+ the content.
+- Explanatory layouts use no more than two content columns. Dense comparison
+ structures must include a nearby Markdown table or list as a mobile and
+ assistive-technology fallback.
+
+Main flows read left to right and then top to bottom. Node titles are normally
+22–26 SVG units and supporting copy 18–20 units at a 960-unit view box.
+Vertical space is reserved for authority, evidence, qualifications, and
+legends rather than for decorative whitespace.
+
+## Connector grammar
+
+| Connector | Meaning |
+| --- | --- |
+| Solid arrow | Authorized process or execution sequence |
+| Dashed arrow | Reporting, recording, qualification, or evidence relationship |
+| Amber line | Human authority entering a decision boundary |
+| Cyan line | Read-only audit or evidence inspection |
+| Neutral line | Implementation or structural flow |
+
+Arrows never imply that approval automatically performs merge, publication,
+deployment, or release. Those actions remain distinct.
+
+## Status vocabulary
+
+Visuals use the same bounded language as the public record:
+
+- `SUPPORTED`
+- `QUALIFIED`
+- `DESIGN-CONTRACT`
+- `COMMITTED-RECORD`
+- `BASELINE-GIT-VERIFIED`
+- `OWNER-CONFIRMED`
+- `LIMITATION`
+- `NOT_PERFORMED`
+
+The words *certified*, *formally verified*, *fully secure*, *zero risk*, and
+*fully isolated* are not used unless the corresponding claim is established by
+an authorized independent source within an explicit scope.
+
+## Accessibility contract
+
+Every SVG must:
+
+- declare `role="img"` and a resolving `aria-labelledby` value;
+- begin with a non-empty `
` and ``;
+- preserve a logical reading order in the document tree;
+- use text labels in addition to color;
+- avoid text smaller than 15 SVG units in a 960-unit view box;
+- contain no flashing, autoplay, or time-dependent content; and
+- remain understandable when printed in grayscale.
+
+The Markdown page embedding the image must also provide concise alt text.
+
+## Registered visual set
+
+| Visual | Primary job | Default placement |
+| --- | --- | --- |
+| System map | Show the governed work-to-release path and evidence spine | Repository README |
+| Recorded outcomes | Present bounded recorded results and their limitations | Repository README |
+| Evidence and release chain | Separate claim discipline, release identity, persistence, and independent scrutiny | Repository README and Assurance Method |
+| Authority matrix | Make non-interchangeable roles and decision rights explicit | System Overview |
+| Social preview | Identify the work and accountable professional role when a repository link is shared | GitHub repository settings |
+
+## Review and change control
+
+1. Draft the visual only from approved public sources.
+2. Register its claims, sources, non-claims, and alt text.
+3. Run the publication verifier and visual-specific tests.
+4. Render the complete SVG and inspect it at desktop and reduced width.
+5. Obtain owner review before changing the metadata status to
+ `OWNER_APPROVED`.
+6. Treat a material visual correction as a new public change. Never rewrite the
+ immutable `v1.0.0` Release or its archived assets.
+
+The exact `v1.0.0` Release, signed release commit, signed annotated tag,
+checksums, provenance, DOI records, and private evidence remain outside this
+visual update.
diff --git a/assets/visuals/authority-matrix.svg b/assets/visuals/authority-matrix.svg
new file mode 100644
index 0000000..f3a40e9
--- /dev/null
+++ b/assets/visuals/authority-matrix.svg
@@ -0,0 +1,183 @@
+
diff --git a/assets/visuals/evidence-release-chain.svg b/assets/visuals/evidence-release-chain.svg
new file mode 100644
index 0000000..3182b73
--- /dev/null
+++ b/assets/visuals/evidence-release-chain.svg
@@ -0,0 +1,133 @@
+
diff --git a/assets/visuals/manifest.json b/assets/visuals/manifest.json
new file mode 100644
index 0000000..331f781
--- /dev/null
+++ b/assets/visuals/manifest.json
@@ -0,0 +1,140 @@
+{
+ "schema_version": "1.0.0",
+ "record_id": "AEAS-VISUAL-MANIFEST-POST-RELEASE-001",
+ "record_scope": "POST_RELEASE_DOCUMENTATION_REFINEMENT",
+ "release_boundary": "This manifest and the registered visuals are later default-branch documentation refinements; they do not alter the v1.0.0 Release, its signed release commit and signed annotated tag, assets, checksums, provenance, or DOI archive.",
+ "independent_review_status": "NOT_PERFORMED",
+ "visuals": [
+ {
+ "id": "authority-matrix",
+ "file": "assets/visuals/authority-matrix.svg",
+ "title": "AEAS authority and control matrix",
+ "description": "Seven action rows distinguish the Assurance Owner, implementation role, read-only audit role, and durable control output. The owner decides objectives, criteria, continuation, risk, completion, and release authorization. Implementation performs bounded work. Audit reports findings without changing source or substituting owner authority. Durable records and automation preserve status but cannot approve. Operational separation is not institutional independence.",
+ "width": 960,
+ "height": 2380,
+ "view_box": "0 0 960 2380",
+ "claim_ids": [
+ "BND-001",
+ "GOV-001",
+ "REL-001",
+ "SYS-001"
+ ],
+ "public_sources": [
+ "PUBLICATION_GATE.md",
+ "README.md",
+ "docs/01-system-overview.md",
+ "docs/03-assurance-method.md"
+ ],
+ "does_not_establish": [
+ "External institutional independence or certification.",
+ "That records or automation possess human decision authority."
+ ],
+ "approval_status": "OWNER_APPROVED"
+ },
+ {
+ "id": "evidence-release-chain",
+ "file": "assets/visuals/evidence-release-chain.svg",
+ "title": "AEAS evidence and release boundaries",
+ "description": "Three visibly separate groups explain AEAS claim discipline, the later public release record, and the current independent-review status. Public claims are tied to evidence classes, sources, and explicit non-claims. The later record documents owner authorization, the signed release commit and signed annotated tag, an immutable GitHub Release with checksums and an artifact-attestation workflow, and a version DOI. Independent review remains not performed. The diagram states that integrity, provenance, and persistence are not correctness; a DOI is not certification or proof of byte equality; and the later publication record is distinct from the v1.0.0 Release and its signed Git objects.",
+ "width": 960,
+ "height": 1540,
+ "view_box": "0 0 960 1540",
+ "claim_ids": [
+ "ARC-001",
+ "BND-001",
+ "GOV-001",
+ "REL-001",
+ "REL-003",
+ "REV-001"
+ ],
+ "public_sources": [
+ "README.md",
+ "docs/03-assurance-method.md",
+ "docs/04-public-evidence-index.md",
+ "docs/06-independent-review.md",
+ "evidence/publication.json",
+ "review/REVIEW_BRIEF.md"
+ ],
+ "does_not_establish": [
+ "Software correctness, defect-free operation, completed independent review, or certification.",
+ "Byte identity between distinct archives or public access to the sealed private baseline."
+ ],
+ "approval_status": "OWNER_APPROVED"
+ },
+ {
+ "id": "recorded-outcomes",
+ "file": "assets/visuals/recorded-outcomes.svg",
+ "title": "AEAS recorded assurance outcomes",
+ "description": "A bounded summary of retained records: seven preserved reports across three governed reviews, six substantive audit rounds and one attempted round recorded as not auditable; all six substantive reports recorded changes-required verdicts; thirty-three findings split into eleven high and twenty-two medium with no critical finding recorded; thirty-two findings marked fixed and one settled through an owner-ratified criteria revision; three final-round findings recorded as defects introduced by earlier corrections; a final governed review with two owner-authorized rounds and eleven medium findings, six in the first round and five in the final round; and 1,111 tests reported green in each of the hermetic and explicit live-host tiers, shown as separate counts and never totaled. The revised criterion is not presented as originally met, and final-round corrections did not receive a third audit round within the same review.",
+ "width": 960,
+ "height": 1960,
+ "view_box": "0 0 960 1960",
+ "claim_ids": [
+ "AUD-001",
+ "AUD-002",
+ "AUD-003",
+ "AUD-004",
+ "AUD-005",
+ "GOV-002",
+ "TST-001"
+ ],
+ "public_sources": [
+ "README.md",
+ "docs/02-implementation-record.md",
+ "docs/03-assurance-method.md",
+ "docs/04-public-evidence-index.md"
+ ],
+ "does_not_establish": [
+ "The absence of undiscovered defects or equal operational impact across findings.",
+ "Independent replay of the reported tests or external certification.",
+ "That the revised criterion was originally met.",
+ "That final-round corrections received a third audit round within the same review."
+ ],
+ "approval_status": "OWNER_APPROVED"
+ },
+ {
+ "id": "social-preview",
+ "file": "assets/visuals/social-preview.png",
+ "title": "Agentic Engineering Assurance System social preview",
+ "description": "Repository social preview identifying Agentic Engineering Assurance System, its governance, auditability, and release-integrity focus, and Ata Nasseri as System Designer and Assurance Owner.",
+ "width": 1280,
+ "height": 640,
+ "view_box": null,
+ "claim_ids": [
+ "SYS-001"
+ ],
+ "public_sources": [
+ "README.md"
+ ],
+ "does_not_establish": [
+ "Runtime topology, independent review, external certification, or software correctness."
+ ],
+ "approval_status": "OWNER_APPROVED"
+ },
+ {
+ "id": "system-map",
+ "file": "assets/visuals/system-map.svg",
+ "title": "AEAS governed work-to-release system map",
+ "description": "A conceptual control model. A bounded work package moves through implementation, an exact source checkpoint and handoff, read-only technical audit, finding disposition, an owner decision, terminal approval, and a separate release action. Human authority is shown above the process and durable records are shown separately below it.",
+ "width": 960,
+ "height": 1500,
+ "view_box": "0 0 960 1500",
+ "claim_ids": [
+ "BND-001",
+ "GOV-001",
+ "REL-001",
+ "SYS-001"
+ ],
+ "public_sources": [
+ "README.md",
+ "docs/01-system-overview.md",
+ "docs/03-assurance-method.md"
+ ],
+ "does_not_establish": [
+ "Private operating topology, external institutional independence, or certification.",
+ "That terminal approval itself performs merge, tag, publication, deployment, or release."
+ ],
+ "approval_status": "OWNER_APPROVED"
+ }
+ ]
+}
diff --git a/assets/visuals/recorded-outcomes.svg b/assets/visuals/recorded-outcomes.svg
new file mode 100644
index 0000000..d023873
--- /dev/null
+++ b/assets/visuals/recorded-outcomes.svg
@@ -0,0 +1,116 @@
+
diff --git a/assets/visuals/social-preview.png b/assets/visuals/social-preview.png
new file mode 100644
index 0000000..4cdbad7
Binary files /dev/null and b/assets/visuals/social-preview.png differ
diff --git a/assets/visuals/system-map.svg b/assets/visuals/system-map.svg
new file mode 100644
index 0000000..9215521
--- /dev/null
+++ b/assets/visuals/system-map.svg
@@ -0,0 +1,128 @@
+
diff --git a/docs/01-system-overview.md b/docs/01-system-overview.md
index b0368c9..64fcb86 100644
--- a/docs/01-system-overview.md
+++ b/docs/01-system-overview.md
@@ -1,6 +1,6 @@
# System Overview
-
+
## Purpose
@@ -49,13 +49,19 @@ approve completion, or authorize release.
## Authority boundaries
-```mermaid
-flowchart LR
- O["Owner"] -->|defines and decides| C["Control boundary"]
- I["Implementation"] -->|changes source| C
- A["Audit"] -->|reports only| C
- C -->|records| G["Git evidence"]
-```
+
+
+[Open the full-size authority matrix](../assets/visuals/authority-matrix.svg).
+
+| Governed action | Assurance owner | Implementation role | Read-only audit role | Durable output or control |
+| --- | --- | --- | --- | --- |
+| Objectives, constraints, and criteria | **Decides** | Works within the boundary | Assesses against the boundary | Bounded work package |
+| Source, tests, handoff, and remediation | Defines the boundary | **Performs** the change | **No source modification** | Exact-source checkpoint and handoff |
+| Audit round | **Authorizes** the round | Supplies pinned handoff and remediation | **Reports only** findings and severity | Audit record tied to exact source |
+| Finding disposition | **Decides** criteria, continuation, and risk | Corrects or records response | Cannot substitute for owner authority | Resolution and decision record |
+| Completion approval | **Decides separately** | Cannot issue owner approval | Cannot issue owner approval | Terminal approval record |
+| Release authorization | **Decides separately** | Cannot issue release authority | Cannot issue release authority | Separate merge, tag, and release identity |
+| Automation status | Retains owner approval authority | Cannot issue owner approval | Cannot issue owner approval | Automation **reports status and cannot issue owner authority** |
Operational separation is not the same as institutional independence. The
audit role is technically separated and read-only inside the designed
diff --git a/docs/03-assurance-method.md b/docs/03-assurance-method.md
index 46eec25..083ee7c 100644
--- a/docs/03-assurance-method.md
+++ b/docs/03-assurance-method.md
@@ -1,6 +1,6 @@
# Assurance Method
-
+
## Objective
@@ -23,17 +23,20 @@ the reviewed source. Approval does not itself merge or release anything.
## Evidence chain
-```mermaid
-flowchart TD
- S["Authorized scope"] --> C["Exact source checkpoint"]
- C --> H["Integrity-bound handoff"]
- H --> F["Audit findings"]
- F --> D["Disposition and decision"]
- D --> R["Signed release identity"]
-```
+
-Each link establishes a different fact. The chain is credible only when those
-differences remain visible.
+[Open the full-size evidence-and-release visual](../assets/visuals/evidence-release-chain.svg).
+
+| Group | Sequence | Boundary preserved |
+| --- | --- | --- |
+| Public claim discipline | Precise claim and ID → evidence class and status → public source and explicit non-claims | An opaque private-baseline digest is a commitment, not public access or independent validation. |
+| Later public release record | Owner authorization → signed release commit and signed annotated tag → immutable GitHub Release, checksums, and artifact-attestation workflow → version DOI | Approval is not release; the later publication record does not alter the `v1.0.0` Release or its signed Git objects. |
+| Independent scrutiny | Qualified external reviewer → independently controlled conclusion | Current status is `NOT_PERFORMED`; no external conclusion or certification is claimed. |
+
+These groups establish different kinds of facts and must not be collapsed into
+one unbroken proof chain. Integrity, provenance, and persistence strengthen
+object identity; they do not establish software correctness. A DOI does not
+establish certification or byte identity between distinct archives.
## Public evidence classes
@@ -99,8 +102,8 @@ The public system record adds a separate assurance layer:
2. allow-list derivation;
3. prohibited-content scan;
4. machine-readable claims;
-5. signed commit and annotated tag;
-6. immutable GitHub release;
+5. signed release commit and signed annotated tag;
+6. immutable GitHub Release;
7. SHA-256 release manifest;
8. GitHub artifact attestation;
9. DOI-backed archival; and
diff --git a/docs/04-public-evidence-index.md b/docs/04-public-evidence-index.md
index 0be22c2..8454747 100644
--- a/docs/04-public-evidence-index.md
+++ b/docs/04-public-evidence-index.md
@@ -1,6 +1,6 @@
# Public Evidence Index
-
+
## Purpose
@@ -36,6 +36,9 @@ private manifest or prove its substantive accuracy.
| `OPS-002` | The owner separately confirmed the final application-visible state. | `OWNER-CONFIRMED` | Human observation is not cryptographic remote attestation. |
| `REL-001` | Approval and release were modeled as separate controlled actions. | `DESIGN-CONTRACT` | Approval alone does not prove that a release occurred. |
| `REL-002` | The completion record reports a versioned correctness release. | `COMMITTED-RECORD` | The public repository does not independently reproduce the private release. |
+| `REL-003` | The later publication record records the signed release commit, signed annotated tag, immutable GitHub Release, checksums, and artifact-attestation workflow for `v1.0.0`. | `COMMITTED-RECORD` | These controls establish identity, integrity, and provenance, not software correctness or independent review. |
+| `ARC-001` | The later publication record identifies the version and concept DOI for this public record. | `COMMITTED-RECORD` | DOI persistence is not certification or proof that distinct archives are byte-identical. |
+| `REV-001` | Independent technical review has not been performed and no external certification is claimed. | `LIMITATION` | A prepared review package is not a completed review or a predicted reviewer conclusion. |
| `BND-001` | The implemented assurance has explicit isolation, liveness, evidence, and review limits. | `LIMITATION` | The system is not presented as fully isolated, formally verified, or defect-free. |
| `SYS-002` | The completion record reports the capability categories summarized here. | `COMMITTED-RECORD` | The summary is not a public reconstruction of the private implementation. |
diff --git a/docs/06-independent-review.md b/docs/06-independent-review.md
index aa5ed7e..2754b31 100644
--- a/docs/06-independent-review.md
+++ b/docs/06-independent-review.md
@@ -1,13 +1,29 @@
# Independent Review
-> **Current status:** Not yet performed
+
-## Intended assurance level
+> **Current status:** `NOT_PERFORMED`
+>
+> No external reviewer has been appointed, no independent conclusion has been
+> issued, and this repository makes no claim of certification.
+
+## Purpose
-The planned review is an **Independent Technical Assurance Review** of a
-specific signed public release and selected supporting private evidence.
+The planned engagement is a scoped independent technical review of Release
+`v1.0.0`, selected supporting private evidence, and the later public record that
+documents the observed Release and DOI.
-It is not automatically:
+The signed release commit and the later post-release publication-record commit
+are different objects. The reviewer must bind the technical conclusion to the
+commit reached by the signed `v1.0.0` tag and identify the later record only as
+post-release evidence. Release and archival identities are recorded in
+[`evidence/publication.json`](../evidence/publication.json); that file does not
+identify the later publication-record commit, which the reviewer must derive
+independently from Git history.
+
+## Intended assurance level
+
+The review is not automatically:
- an ISO certification;
- a SOC examination;
@@ -25,7 +41,8 @@ The reviewer will assess whether:
2. aggregate findings and dispositions are accurately represented;
3. evidence qualifications and non-claims are preserved;
4. the public repository avoids prohibited private detail;
-5. the signed release, digest manifest, and DOI identify the reviewed object;
+5. the signed release commit, signed annotated tag, Release, digest manifest,
+ and DOI identify the reviewed object;
and
6. the limitations are sufficient for a reasonable reader to understand the
assurance boundary.
@@ -46,20 +63,38 @@ The public report must identify:
- reviewer name, organization, role, and relevant qualifications;
- conflict-of-interest declaration;
-- exact repository, commit, signed tag, release, and DOI;
+- exact repository, release commit, signed tag, tag object, Release, and
+ version DOI;
+- the later post-release publication-record commit as a separate object;
- review dates and methods;
-- evidence sampled;
+- evidence inspected and independently selected samples;
- findings and qualifications;
- excluded scope;
- conclusion; and
-- reviewer signature or independently verifiable digital approval.
+- reviewer-controlled signature or independently verifiable digital approval.
+
+The conclusion vocabulary is:
+
+- `CONFIRMED`: sufficient evidence was obtained and no unresolved material
+ inconsistency was identified within scope.
+- `CONFIRMED WITH QUALIFICATIONS`: the core conclusion is supportable, but
+ stated evidence or scope limitations materially narrow reliance.
+- `NOT CONFIRMED`: evidence was insufficient or an unresolved material
+ inconsistency was identified.
-The expected conclusion vocabulary is:
+## Publication rule
-- `CONFIRMED`;
-- `CONFIRMED WITH QUALIFICATIONS`; or
-- `NOT CONFIRMED`.
+The reviewer controls the wording and conclusion of the final statement. The
+owner may publish an unmodified copy and a clearly separated owner response,
+but may not edit the reviewer's report.
-Until that report exists, the repository will not describe itself as
-independently reviewed or certified.
+The public statement must have a stable URL and a reviewer-controlled signature
+or independently verifiable approval. Its SHA-256 digest must be published
+outside the report itself in a signed checksum, Git, release-manifest, detached
+signature, or equivalent verification record. If a confidential annex affects
+the conclusion, the public statement must disclose its existence and effect
+without exposing sensitive evidence.
+Only after that statement has been verified may a successor commit change the
+machine-readable status from `NOT_PERFORMED`. Release `v1.0.0` remains
+unchanged.
diff --git a/docs/07-references.md b/docs/07-references.md
index 9158867..9e66627 100644
--- a/docs/07-references.md
+++ b/docs/07-references.md
@@ -19,9 +19,8 @@ release. They do not imply endorsement, conformity, or certification.
## Standards context
-- [ISO/IEC 42001 — AI management systems](https://www.iso.org/standard/42001)
-- [ISO/IEC 27001 — information security management systems](https://www.iso.org/standard/27001)
+- [ISO/IEC 42001: AI management systems](https://www.iso.org/standard/42001)
+- [ISO/IEC 27001: information security management systems](https://www.iso.org/standard/27001)
The system record does not claim conformity with either ISO standard. They are
listed only as future organizational governance context.
-
diff --git a/evidence/public-claims.json b/evidence/public-claims.json
index 46f0f21..8a87d0e 100644
--- a/evidence/public-claims.json
+++ b/evidence/public-claims.json
@@ -8,12 +8,30 @@
"does_not_establish": "The digest does not disclose or independently validate the private baseline."
},
"claims": [
+ {
+ "id": "ARC-001",
+ "statement": "The later public publication record identifies 10.5281/zenodo.22019208 as the version DOI for Release v1.0.0 and 10.5281/zenodo.22019207 as the concept DOI.",
+ "evidence_class": "COMMITTED-RECORD",
+ "status": "QUALIFIED",
+ "public_sources": [
+ "README.md",
+ "docs/03-assurance-method.md",
+ "docs/04-public-evidence-index.md",
+ "evidence/publication.json",
+ "review/REVIEW_BRIEF.md"
+ ],
+ "does_not_establish": [
+ "Certification, correctness, or independent technical review.",
+ "Byte identity between Zenodo's generated source archive and the attested GitHub release bundle."
+ ]
+ },
{
"id": "AUD-001",
"statement": "Seven audit reports were preserved across three governed reviews.",
"evidence_class": "BASELINE-GIT-VERIFIED",
"status": "SUPPORTED",
"public_sources": [
+ "README.md",
"docs/02-implementation-record.md",
"docs/04-public-evidence-index.md"
],
@@ -58,6 +76,7 @@
"evidence_class": "COMMITTED-RECORD",
"status": "QUALIFIED",
"public_sources": [
+ "README.md",
"docs/02-implementation-record.md",
"docs/04-public-evidence-index.md"
],
@@ -86,6 +105,8 @@
"evidence_class": "LIMITATION",
"status": "QUALIFIED",
"public_sources": [
+ "README.md",
+ "docs/01-system-overview.md",
"docs/05-limitations-and-reassessment.md"
],
"does_not_establish": [
@@ -98,6 +119,7 @@
"evidence_class": "DESIGN-CONTRACT",
"status": "QUALIFIED",
"public_sources": [
+ "README.md",
"docs/01-system-overview.md",
"docs/03-assurance-method.md",
"docs/04-public-evidence-index.md"
@@ -113,6 +135,7 @@
"evidence_class": "COMMITTED-RECORD",
"status": "QUALIFIED",
"public_sources": [
+ "README.md",
"docs/03-assurance-method.md",
"docs/04-public-evidence-index.md"
],
@@ -154,6 +177,8 @@
"evidence_class": "DESIGN-CONTRACT",
"status": "QUALIFIED",
"public_sources": [
+ "PUBLICATION_GATE.md",
+ "README.md",
"docs/01-system-overview.md",
"docs/03-assurance-method.md",
"docs/04-public-evidence-index.md"
@@ -176,6 +201,40 @@
"That the public technical record has itself been released."
]
},
+ {
+ "id": "REL-003",
+ "statement": "The later public publication record records the signed release commit, signed annotated tag, immutable GitHub Release, SHA-256 release checksums, and GitHub artifact-attestation workflow for v1.0.0.",
+ "evidence_class": "COMMITTED-RECORD",
+ "status": "QUALIFIED",
+ "public_sources": [
+ "README.md",
+ "docs/03-assurance-method.md",
+ "docs/04-public-evidence-index.md",
+ "evidence/publication.json",
+ "review/REVIEW_BRIEF.md"
+ ],
+ "does_not_establish": [
+ "Software correctness, defect-free operation, or independent technical review.",
+ "That the later default-branch publication record is part of the v1.0.0 Release or its signed Git objects."
+ ]
+ },
+ {
+ "id": "REV-001",
+ "statement": "The later public record states that independent technical review has not been performed and that no external certification is claimed.",
+ "evidence_class": "LIMITATION",
+ "status": "QUALIFIED",
+ "public_sources": [
+ "README.md",
+ "docs/03-assurance-method.md",
+ "docs/04-public-evidence-index.md",
+ "docs/06-independent-review.md",
+ "evidence/publication.json"
+ ],
+ "does_not_establish": [
+ "That the prepared review package constitutes a completed review.",
+ "Any predicted conclusion of a future independent reviewer."
+ ]
+ },
{
"id": "SYS-001",
"statement": "The system design separates owner authority, implementation, read-only audit, durable evidence, and release.",
diff --git a/review/ASSURANCE_STATEMENT_TEMPLATE.md b/review/ASSURANCE_STATEMENT_TEMPLATE.md
index 5c1e6a2..d9f77b1 100644
--- a/review/ASSURANCE_STATEMENT_TEMPLATE.md
+++ b/review/ASSURANCE_STATEMENT_TEMPLATE.md
@@ -1,38 +1,54 @@
-# Independent Technical Assurance Statement
+# Independent Technical Review Statement
+
+> This is a scoped independent technical review statement. It is not a
+> certification, audit opinion, penetration test, legal opinion, or warranty.
## Identification
-**System record:** Agentic Engineering Assurance System
-**Repository:** `https://github.com/Atanasseri/agentic-engineering-assurance-system`
-**Release:** `[exact release]`
-**Commit:** `[full commit SHA]`
-**Signed tag:** `[exact annotated tag]`
-**DOI:** `[version DOI]`
-**Review period:** `[start date]` to `[end date]`
+| Field | Value |
+| --- | --- |
+| System record | Agentic Engineering Assurance System |
+| Repository | `https://github.com/Atanasseri/agentic-engineering-assurance-system` |
+| Release | `v1.0.0` |
+| Reviewed Release commit | `[full commit SHA]` |
+| Post-release publication-record commit | `[full commit SHA]` |
+| Signed tag | `v1.0.0` |
+| Tag object SHA | `[full tag-object SHA]` |
+| Version DOI | `10.5281/zenodo.22019208` |
+| Review period | `[start date]` to `[end date]` |
+| Report version | `[report version]` |
+| Public report URL | `[stable URL]` |
+| External checksum or verification record | `[stable URL or signed manifest]` |
## Reviewer
-**Name:** `[reviewer name]`
-**Organization:** `[organization]`
-**Role:** `[role]`
-**Relevant qualifications:** `[qualifications and experience]`
+- **Name:** `[reviewer name]`
+- **Organization:** `[organization]`
+- **Role:** `[role]`
+- **Relevant qualifications:** `[qualifications and experience]`
## Independence declaration
-`[Describe any financial, professional, personal, authorship, or implementation
-relationship relevant to the review. State whether the reviewer had any role in
-designing or implementing the reviewed system.]`
+`[Describe any financial, professional, personal, authorship, implementation,
+or compensation relationship relevant to the review. State whether the
+reviewer had any role in designing or implementing the reviewed system.]`
+
+## Evidence and method
-## Scope and method
+- **Public materials inspected:** `[list]`
+- **Private evidence categories inspected:** `[non-sensitive identifiers]`
+- **Quantitative claims independently recalculated:** `[list or all]`
+- **Reviewer-selected samples:** `[selection method]`
+- **Evidence unavailable or withheld:** `[none or list]`
+- **Automated or AI assistance used:** `[none or disclosure]`
-`[Describe the public release inspected, the private evidence sampled, the
-verification methods used, and any limits on evidence access.]`
+`[Describe the verification methods used and any limits on evidence access.]`
## Findings
-| ID | Severity | Finding | Evidence | Disposition |
+| ID | Classification | Observation | Evidence reference | Status at report date |
| --- | --- | --- | --- | --- |
-| `[ID]` | `[severity]` | `[finding]` | `[public or confidential reference]` | `[open, corrected, accepted, or qualified]` |
+| `IR-v1.0.0-001` | `MATERIAL / LIMITED / OBSERVATION` | `[text]` | `[public or blinded confidential reference]` | `[open, acknowledged, or corrected in successor release]` |
## Qualifications and exclusions
@@ -41,21 +57,23 @@ on owner-confirmed or committed-record evidence.]`
## Conclusion
-Select exactly one:
+Select exactly one using the definitions in
+[`docs/06-independent-review.md`](../docs/06-independent-review.md):
- `CONFIRMED`
- `CONFIRMED WITH QUALIFICATIONS`
- `NOT CONFIRMED`
-`[Provide a concise conclusion limited to the stated scope. Do not use the word
-certified unless the reviewer is acting through a recognized certification
-scheme that covers the exact claim.]`
+`[Provide a concise conclusion limited to the stated object, evidence, scope,
+and review dates. Do not describe the system as certified.]`
## Signature
-**Reviewer:** `[name]`
-**Date:** `[UTC date]`
-**Signature method:** `[qualified electronic signature, document signature, or
-verifiable signed Git object]`
-**Verification reference:** `[public verification method or fingerprint]`
+- **Reviewer-controlled signature method:** `[signed Git commit, detached
+ digital signature, or reputable document-signing service]`
+- **Signature fingerprint or verification URL:** `[value]`
+- **UTC issue date:** `[YYYY-MM-DD]`
+The final report's SHA-256 digest must be published outside the report itself
+in a signed Git record, release manifest, detached signature record, or other
+independently verifiable checksum record.
diff --git a/review/REVIEW_BRIEF.md b/review/REVIEW_BRIEF.md
index e76540e..d5d0a63 100644
--- a/review/REVIEW_BRIEF.md
+++ b/review/REVIEW_BRIEF.md
@@ -1,4 +1,6 @@
-# Independent Technical Assurance Review Brief
+# Independent Technical Review Brief
+
+
## Commissioning objective
@@ -7,7 +9,42 @@ System is materially consistent with selected sealed private evidence and
whether its claims, qualifications, and limitations are professionally
supportable.
-## Intended reader
+## Engagement record
+
+| Field | Value |
+| --- | --- |
+| Commissioner | Ata Nasseri, Solofounders |
+| Target Release | `v1.0.0` |
+| Expected effort | To be agreed after conflict and evidence-access review |
+| Commercial basis | `[paid, pro bono, or other; disclose in final report]` |
+| Target dates | `[start]` to `[completion]` |
+| Contact | Arranged through direct professional outreach |
+
+## Object identity
+
+| Object | Canonical reference |
+| --- | --- |
+| Repository | `https://github.com/Atanasseri/agentic-engineering-assurance-system` |
+| Release | `v1.0.0` |
+| Immutable GitHub Release | `https://github.com/Atanasseri/agentic-engineering-assurance-system/releases/tag/v1.0.0` |
+| Release-evidence workflow | `https://github.com/Atanasseri/agentic-engineering-assurance-system/actions/runs/32308546078` |
+| Release bundle SHA-256 | `598ca929037c78b856297143c932b54900cdc02ec99d36fb7dd132be2c7b10a9` |
+| `SHA256SUMS` SHA-256 | `bc903bdbd0810e3800e0ebf3762c5a45b6ccfbe5a27380ee220a4e29b8d35d73` |
+| Version DOI | `10.5281/zenodo.22019208` |
+| Zenodo record | `https://zenodo.org/records/22019208` |
+| Concept DOI, all versions | `10.5281/zenodo.22019207` |
+| Machine-readable release identities | [`evidence/publication.json`](../evidence/publication.json) |
+
+The reviewer must independently record the full release commit, annotated-tag
+object, and later post-release publication-record commit. These are distinct
+objects and must not be substituted for one another.
+
+Zenodo's automatically generated source ZIP and the attested GitHub release
+bundle are also distinct artifacts. The DOI establishes archival identity; it
+does not establish that the two archives are byte-identical unless the reviewer
+separately demonstrates that fact.
+
+## Intended reviewer
A senior reviewer with relevant experience in at least two of the following:
@@ -33,6 +70,35 @@ are more important than credentials alone.
consistent?
6. Are limitations adequate for a reasonable technical or professional reader?
+## Review method
+
+The reviewer is expected to:
+
+1. verify the public object identities and signatures independently;
+2. inspect the complete public claim register;
+3. independently recalculate every quantitative claim in scope, or identify
+ each unverified claim as a limitation;
+4. select non-quantitative evidence samples independently rather than accept
+ only owner-selected examples;
+5. record unavailable, withheld, substituted, or ambiguous evidence;
+6. distinguish the attested GitHub release bundle from Zenodo's generated
+ source archive; and
+7. retain sole control over the final conclusion.
+
+Owner responses and later corrections may be published separately. They do not
+rewrite the reviewed historical Release or the reviewer's conclusion.
+
+## Independence and tool disclosure
+
+The reviewer must disclose prior work, personal or commercial relationships,
+authorship, implementation involvement, compensation, and any other condition
+that could reasonably affect perceived independence. A paid engagement is not
+automatically disqualifying, but it must be disclosed.
+
+Any material use of automated or AI-assisted review tools must be disclosed.
+Private evidence must not be submitted to an external AI service or other third
+party without explicit written authorization.
+
## Out of scope unless separately commissioned
- full source-code audit;
@@ -47,9 +113,9 @@ are more important than credentials alone.
## Expected deliverables
- completed review checklist;
-- findings with severity and evidence references;
+- findings with classification and evidence references;
- independence and conflict-of-interest declaration;
-- signed public assurance statement; and
+- signed public technical review statement; and
- optional confidential annex for sensitive observations.
## Evidence handling
@@ -57,4 +123,3 @@ are more important than credentials alone.
The reviewer receives the minimum private evidence required for each sampled
claim. Access is read-only, confidential, and does not grant reuse or
publication rights. Raw evidence should not be copied into the public report.
-
diff --git a/review/REVIEW_CHECKLIST.md b/review/REVIEW_CHECKLIST.md
index bae11a7..8637a86 100644
--- a/review/REVIEW_CHECKLIST.md
+++ b/review/REVIEW_CHECKLIST.md
@@ -3,23 +3,32 @@
## Reviewer identity and independence
- [ ] Name, organization, role, and relevant qualifications recorded.
-- [ ] Commercial, personal, and authorship relationships disclosed.
+- [ ] Commercial, personal, authorship, and implementation relationships disclosed.
- [ ] Reviewer confirms no implementation responsibility for the reviewed release.
- [ ] Any limitation on independence is described.
+- [ ] Compensation basis is disclosed.
## Object identity
- [ ] Canonical repository confirmed.
-- [ ] Full release commit recorded.
-- [ ] Signed annotated tag verified.
-- [ ] Immutable GitHub release verified.
+- [ ] Full release commit independently recorded.
+- [ ] Later post-release publication-record commit recorded separately.
+- [ ] Signed annotated tag and tag-object identity verified.
+- [ ] Immutable GitHub Release verified.
- [ ] Release asset SHA-256 values verified.
- [ ] Artifact provenance verified.
-- [ ] DOI resolves to the reviewed release.
+- [ ] Version DOI `10.5281/zenodo.22019208` resolves to the `v1.0.0` record.
+- [ ] The concept DOI is treated only as the all-versions identifier.
+- [ ] The Zenodo-generated archive and attested GitHub bundle are not presented
+ as byte-identical unless separately verified.
-## Claim sampling
+## Claim evaluation
-- [ ] All numerical claims sampled against the private baseline.
+- [ ] Every quantitative public claim in scope was independently recalculated.
+- [ ] Every quantitative exception is listed as an evidence limitation.
+- [ ] Non-quantitative samples were selected by the reviewer after reviewing
+ the complete claim register.
+- [ ] Unavailable, withheld, substituted, or ambiguous evidence is recorded.
- [ ] Audit-report count and severity totals independently recounted.
- [ ] Finding dispositions sampled against resolution records.
- [ ] Criteria revision remains distinguishable from a fix.
@@ -35,6 +44,17 @@
- [ ] No raw transcript, pane capture, or private decision text is published.
- [ ] Diagrams do not reconstruct private topology.
+## Evidence handling record
+
+- [ ] Access method and access dates recorded.
+- [ ] Evidence categories inspected are listed using non-sensitive references.
+- [ ] Confidentiality, retention, and deletion obligations recorded.
+- [ ] No private evidence was uploaded to an external AI or cloud service
+ without explicit authorization.
+- [ ] Material automated assistance is disclosed.
+- [ ] The final report SHA-256 is recorded outside the report in a signed or
+ independently verifiable checksum record.
+
## Method and limitations
- [ ] Evidence classes are understandable and consistently applied.
@@ -43,12 +63,21 @@
- [ ] Operational separation is not presented as institutional independence.
- [ ] External certification is not claimed.
+## Finding classification
+
+- `MATERIAL`: could change the conclusion or make a public claim misleading.
+- `LIMITED`: requires correction or qualification but does not by itself change
+ the core conclusion.
+- `OBSERVATION`: improvement opportunity without a supported inconsistency.
+
## Conclusion
+Select exactly one using the definitions in
+[`docs/06-independent-review.md`](../docs/06-independent-review.md):
+
- [ ] `CONFIRMED`
- [ ] `CONFIRMED WITH QUALIFICATIONS`
- [ ] `NOT CONFIRMED`
Every qualification and unresolved finding must be listed in the final
statement or its referenced public annex.
-
diff --git a/schemas/visual-manifest.schema.json b/schemas/visual-manifest.schema.json
new file mode 100644
index 0000000..d8a5cfb
--- /dev/null
+++ b/schemas/visual-manifest.schema.json
@@ -0,0 +1,129 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://github.com/Atanasseri/agentic-engineering-assurance-system/schemas/visual-manifest.schema.json",
+ "title": "Agentic Engineering Assurance Visual Manifest",
+ "type": "object",
+ "required": [
+ "schema_version",
+ "record_id",
+ "record_scope",
+ "release_boundary",
+ "independent_review_status",
+ "visuals"
+ ],
+ "properties": {
+ "schema_version": {
+ "const": "1.0.0"
+ },
+ "record_id": {
+ "type": "string",
+ "minLength": 1
+ },
+ "record_scope": {
+ "const": "POST_RELEASE_DOCUMENTATION_REFINEMENT"
+ },
+ "release_boundary": {
+ "type": "string",
+ "minLength": 1
+ },
+ "independent_review_status": {
+ "enum": [
+ "NOT_PERFORMED",
+ "IN_PROGRESS",
+ "COMPLETED"
+ ]
+ },
+ "visuals": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "file",
+ "title",
+ "description",
+ "width",
+ "height",
+ "view_box",
+ "claim_ids",
+ "public_sources",
+ "does_not_establish",
+ "approval_status"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
+ },
+ "file": {
+ "type": "string",
+ "pattern": "^assets/visuals/[a-z0-9-]+\\.(svg|png)$"
+ },
+ "title": {
+ "type": "string",
+ "minLength": 1
+ },
+ "description": {
+ "type": "string",
+ "minLength": 1
+ },
+ "width": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "view_box": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^0 0 [1-9][0-9]* [1-9][0-9]*$"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "claim_ids": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "pattern": "^[A-Z]{3}-[0-9]{3}$"
+ },
+ "uniqueItems": true
+ },
+ "public_sources": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ },
+ "uniqueItems": true
+ },
+ "does_not_establish": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "approval_status": {
+ "enum": [
+ "DRAFT",
+ "OWNER_APPROVED",
+ "SUPERSEDED"
+ ]
+ }
+ },
+ "additionalProperties": false
+ }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/tests/test_verify_publication.py b/tests/test_verify_publication.py
index 82c1bf7..9126145 100644
--- a/tests/test_verify_publication.py
+++ b/tests/test_verify_publication.py
@@ -237,6 +237,370 @@ def test_disallowed_positioning_phrase_fails(self) -> None:
path.write_text(path.read_text(encoding="utf-8") + f"\nA research {phrase}.\n", encoding="utf-8")
self.assertTrue(any("disallowed positioning phrase" in error for error in verify(self.root)))
+ def test_em_dash_character_fails(self) -> None:
+ path = self.root / "README.md"
+ prohibited_character = chr(0x2014)
+ path.write_text(
+ path.read_text(encoding="utf-8")
+ + f"\nAuthority {prohibited_character} implementation.\n",
+ encoding="utf-8",
+ )
+ self.assertTrue(any("prohibited em dash character" in error for error in verify(self.root)))
+
+ def test_missing_visual_manifest_fails(self) -> None:
+ (self.root / "assets/visuals/manifest.json").unlink()
+ self.assertTrue(
+ any(
+ "missing required file: assets/visuals/manifest.json" in error
+ for error in verify(self.root)
+ )
+ )
+
+ def test_unregistered_visual_fails(self) -> None:
+ source = self.root / "assets/visuals/system-map.svg"
+ target = self.root / "assets/visuals/unregistered.svg"
+ shutil.copyfile(source, target)
+ self.assertTrue(any("unregistered visual asset" in error for error in verify(self.root)))
+
+ def test_visual_unknown_claim_fails(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ data["visuals"][0]["claim_ids"].append("ZZZ-999")
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(
+ any("references unknown public claim" in error for error in verify(self.root))
+ )
+
+ def test_visual_claim_requires_supporting_source(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ data["visuals"][0]["public_sources"] = ["docs/02-implementation-record.md"]
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(
+ any("unsupported by its declared public_sources" in error for error in verify(self.root))
+ )
+
+ def test_visual_requires_nonclaim(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ data["visuals"][0]["does_not_establish"] = []
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(any("visual non-claim" in error for error in verify(self.root)))
+
+ def test_visual_review_status_matches_publication(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ data["independent_review_status"] = "COMPLETED"
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(
+ any("differs from publication record" in error for error in verify(self.root))
+ )
+
+ def test_svg_requires_accessible_title_and_description(self) -> None:
+ path = self.root / "assets/visuals/system-map.svg"
+ text = path.read_text(encoding="utf-8").replace(
+ '',
+ '',
+ 1,
+ ).replace("", "", 1)
+ path.write_text(text, encoding="utf-8")
+ self.assertTrue(any("begin with title and desc" in error for error in verify(self.root)))
+
+ def test_svg_rejects_script(self) -> None:
+ path = self.root / "assets/visuals/system-map.svg"
+ text = path.read_text(encoding="utf-8").replace("", "")
+ path.write_text(text, encoding="utf-8")
+ self.assertTrue(any("prohibited element script" in error for error in verify(self.root)))
+
+ def test_svg_rejects_external_reference(self) -> None:
+ path = self.root / "assets/visuals/system-map.svg"
+ text = path.read_text(encoding="utf-8").replace(
+ "",
+ '',
+ )
+ path.write_text(text, encoding="utf-8")
+ self.assertTrue(any("external reference" in error for error in verify(self.root)))
+
+ def test_visual_manifest_rejects_unsupported_field(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ data["visuals"][0]["uncontrolled_note"] = "not part of the schema"
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(any("unsupported field" in error for error in verify(self.root)))
+
+ def test_visual_rejects_invalid_approval_status(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ data["visuals"][0]["approval_status"] = "REVIEWED"
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(any("invalid approval_status" in error for error in verify(self.root)))
+
+ def test_svg_dimension_mismatch_fails(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ system_map = next(item for item in data["visuals"] if item["id"] == "system-map")
+ system_map["height"] += 1
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(any("SVG height differs" in error for error in verify(self.root)))
+
+ def test_png_dimension_mismatch_fails(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ social_preview = next(
+ item for item in data["visuals"] if item["id"] == "social-preview"
+ )
+ social_preview["width"] += 1
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(any("PNG dimensions differ" in error for error in verify(self.root)))
+
+ def test_local_markdown_image_requires_alt_text(self) -> None:
+ path = self.root / "README.md"
+ path.write_text(
+ path.read_text(encoding="utf-8")
+ + "\n\n",
+ encoding="utf-8",
+ )
+ self.assertTrue(any("empty alt text" in error for error in verify(self.root)))
+
+ def test_broken_local_markdown_image_fails(self) -> None:
+ path = self.root / "README.md"
+ path.write_text(
+ path.read_text(encoding="utf-8")
+ + "\n\n",
+ encoding="utf-8",
+ )
+ self.assertTrue(any("broken local image" in error for error in verify(self.root)))
+
+ def test_encoded_json_private_path_fails(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ encoded_path = "\\u002froot\\u002fprivate-record"
+ text = path.read_text(encoding="utf-8").replace(
+ "Repository social preview identifying",
+ encoded_path + " Repository social preview identifying",
+ 1,
+ )
+ path.write_text(text, encoding="utf-8")
+ self.assertTrue(
+ any("operational-style absolute path" in error for error in verify(self.root))
+ )
+
+ def test_encoded_svg_private_path_fails(self) -> None:
+ path = self.root / "assets/visuals/system-map.svg"
+ slash_entity = "&" + "#47;"
+ payload = f"{slash_entity}root{slash_entity}private-record"
+ path.write_text(
+ path.read_text(encoding="utf-8").replace("", payload + ""),
+ encoding="utf-8",
+ )
+ self.assertTrue(
+ any("operational-style absolute path" in error for error in verify(self.root))
+ )
+
+ def test_svg_rejects_processing_instruction(self) -> None:
+ path = self.root / "assets/visuals/system-map.svg"
+ path.write_text(
+ '\n'
+ + path.read_text(encoding="utf-8"),
+ encoding="utf-8",
+ )
+ self.assertTrue(
+ any("processing instructions" in error for error in verify(self.root))
+ )
+
+ def test_svg_rejects_xml_base(self) -> None:
+ path = self.root / "assets/visuals/system-map.svg"
+ text = path.read_text(encoding="utf-8").replace(
+ 'role="img"',
+ 'xml:base="https://example.invalid/" role="img"',
+ 1,
+ )
+ path.write_text(text, encoding="utf-8")
+ self.assertTrue(any("xml:base" in error for error in verify(self.root)))
+
+ def test_png_rejects_invalid_first_chunk(self) -> None:
+ path = self.root / "assets/visuals/social-preview.png"
+ data = bytearray(path.read_bytes())
+ data[12:16] = b"FAKE"
+ path.write_bytes(data)
+ self.assertTrue(any("must begin with" in error for error in verify(self.root)))
+
+ def test_png_rejects_trailing_payload(self) -> None:
+ path = self.root / "assets/visuals/social-preview.png"
+ path.write_bytes(path.read_bytes() + b"trailing-payload")
+ self.assertTrue(any("end exactly" in error for error in verify(self.root)))
+
+ def test_png_rejects_text_metadata_chunk(self) -> None:
+ path = self.root / "assets/visuals/social-preview.png"
+ data = bytearray(path.read_bytes())
+ first_idat = data.find(b"IDAT")
+ self.assertGreater(first_idat, 0)
+ data[first_idat : first_idat + 4] = b"tEXt"
+ path.write_bytes(data)
+ self.assertTrue(any("unsupported metadata" in error for error in verify(self.root)))
+
+ def test_visual_manifest_malformed_claim_ids_do_not_crash(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ data["visuals"][0]["claim_ids"] = ["SYS-001", 7]
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(any("valid strings" in error for error in verify(self.root)))
+
+ def test_malformed_registered_claim_sources_do_not_crash(self) -> None:
+ path = self.root / "evidence/public-claims.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ claim = next(item for item in data["claims"] if item["id"] == "ARC-001")
+ claim["public_sources"].append({"unexpected": "source"})
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ errors = verify(self.root)
+ self.assertTrue(
+ any(
+ "public_sources must contain only non-empty strings" in error
+ for error in errors
+ )
+ )
+
+ def test_visual_manifest_malformed_approval_status_does_not_crash(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ data["visuals"][0]["approval_status"] = {}
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(any("invalid approval_status" in error for error in verify(self.root)))
+
+ def test_invalid_independent_review_status_fails_even_when_records_match(self) -> None:
+ publication_path = self.root / "evidence/publication.json"
+ publication = json.loads(publication_path.read_text(encoding="utf-8"))
+ publication["independent_review"]["status"] = "BOGUS"
+ publication_path.write_text(
+ json.dumps(publication, indent=2) + "\n", encoding="utf-8"
+ )
+ manifest_path = self.root / "assets/visuals/manifest.json"
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
+ manifest["independent_review_status"] = "BOGUS"
+ manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
+ errors = verify(self.root)
+ self.assertTrue(any("independent review status is invalid" in error for error in errors))
+ self.assertTrue(any("independent_review_status is invalid" in error for error in errors))
+
+ def test_completed_review_requires_evidence_and_retires_not_performed_claim(self) -> None:
+ publication_path = self.root / "evidence/publication.json"
+ publication = json.loads(publication_path.read_text(encoding="utf-8"))
+ publication["independent_review"] = {
+ "status": "COMPLETED",
+ "reviewed_release": None,
+ "public_statement": None,
+ }
+ publication_path.write_text(
+ json.dumps(publication, indent=2) + "\n", encoding="utf-8"
+ )
+
+ manifest_path = self.root / "assets/visuals/manifest.json"
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
+ manifest["independent_review_status"] = "COMPLETED"
+ manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
+
+ errors = verify(self.root)
+ self.assertTrue(any("version-bound release" in error for error in errors))
+ self.assertTrue(any("stable HTTPS public statement" in error for error in errors))
+ self.assertTrue(any("REV-001 NOT_PERFORMED" in error for error in errors))
+
+ def test_visual_rejects_unrelated_declared_source(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ sources = data["visuals"][0]["public_sources"]
+ data["visuals"][0]["public_sources"] = sorted(sources + ["SECURITY.md"])
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(any("source is unrelated" in error for error in verify(self.root)))
+
+ def test_markdown_rejects_superseded_visual(self) -> None:
+ path = self.root / "assets/visuals/manifest.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ system_map = next(item for item in data["visuals"] if item["id"] == "system-map")
+ system_map["approval_status"] = "SUPERSEDED"
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(any("embeds superseded" in error for error in verify(self.root)))
+
+ def test_visual_schema_contract_is_verified(self) -> None:
+ path = self.root / "schemas/visual-manifest.schema.json"
+ path.write_text("{}\n", encoding="utf-8")
+ self.assertTrue(any("schema root contract" in error for error in verify(self.root)))
+
+ def test_svg_rejects_css_animation(self) -> None:
+ path = self.root / "assets/visuals/system-map.svg"
+ text = path.read_text(encoding="utf-8").replace(
+ "",
+ "",
+ )
+ path.write_text(text, encoding="utf-8")
+ self.assertTrue(any("CSS animation" in error for error in verify(self.root)))
+
+ def test_svg_requires_canonical_namespace(self) -> None:
+ path = self.root / "assets/visuals/system-map.svg"
+ text = path.read_text(encoding="utf-8").replace(
+ "http://www.w3.org/2000/svg",
+ "https://example.invalid/not-svg",
+ 1,
+ )
+ path.write_text(text, encoding="utf-8")
+ self.assertTrue(any("canonical SVG namespace" in error for error in verify(self.root)))
+
+ def test_svg_rejects_duplicate_ids(self) -> None:
+ path = self.root / "assets/visuals/system-map.svg"
+ text = path.read_text(encoding="utf-8").replace(
+ 'id="system-map-desc"',
+ 'id="system-map-title"',
+ 1,
+ )
+ path.write_text(text, encoding="utf-8")
+ self.assertTrue(any("ids must be unique" in error for error in verify(self.root)))
+
+ def test_svg_rejects_unresolved_fragment(self) -> None:
+ path = self.root / "assets/visuals/system-map.svg"
+ text = path.read_text(encoding="utf-8").replace(
+ "url(#system-arrow)",
+ "url(#missing-arrow)",
+ 1,
+ )
+ path.write_text(text, encoding="utf-8")
+ self.assertTrue(any("unresolved fragment" in error for error in verify(self.root)))
+
+ def test_svg_viewbox_must_match_dimensions(self) -> None:
+ svg_path = self.root / "assets/visuals/system-map.svg"
+ svg_path.write_text(
+ svg_path.read_text(encoding="utf-8").replace(
+ 'viewBox="0 0 960 1500"',
+ 'viewBox="0 0 1 1"',
+ 1,
+ ),
+ encoding="utf-8",
+ )
+ manifest_path = self.root / "assets/visuals/manifest.json"
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
+ system_map = next(
+ item for item in manifest["visuals"] if item["id"] == "system-map"
+ )
+ system_map["view_box"] = "0 0 1 1"
+ manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
+ self.assertTrue(any("intrinsic dimensions" in error for error in verify(self.root)))
+
+ def test_reference_style_markdown_image_is_rejected(self) -> None:
+ path = self.root / "README.md"
+ path.write_text(
+ path.read_text(encoding="utf-8")
+ + "\n![system map][system-map-ref]\n\n"
+ + "[system-map-ref]: assets/visuals/system-map.svg\n",
+ encoding="utf-8",
+ )
+ self.assertTrue(any("reference-style" in error for error in verify(self.root)))
+
+ def test_raw_html_image_is_rejected(self) -> None:
+ path = self.root / "README.md"
+ path.write_text(
+ path.read_text(encoding="utf-8")
+ + '\n\n',
+ encoding="utf-8",
+ )
+ self.assertTrue(any("raw HTML image" in error for error in verify(self.root)))
+
if __name__ == "__main__":
unittest.main()
diff --git a/tools/verify_publication.py b/tools/verify_publication.py
index d5762f1..98a84b2 100644
--- a/tools/verify_publication.py
+++ b/tools/verify_publication.py
@@ -5,9 +5,13 @@
from __future__ import annotations
import argparse
+import html
import json
import re
+import struct
import sys
+import xml.etree.ElementTree as ET
+import zlib
from pathlib import Path
@@ -28,6 +32,8 @@
"PUBLICATION_GATE.md",
"README.md",
"SECURITY.md",
+ "assets/VISUAL_SYSTEM.md",
+ "assets/visuals/manifest.json",
"docs/01-system-overview.md",
"docs/02-implementation-record.md",
"docs/03-assurance-method.md",
@@ -44,11 +50,12 @@
"review/REVIEW_BRIEF.md",
"review/REVIEW_CHECKLIST.md",
"schemas/public-claims.schema.json",
+ "schemas/visual-manifest.schema.json",
"tests/test_verify_publication.py",
"tools/verify_publication.py",
}
-TEXT_SUFFIXES = {".cff", ".json", ".md", ".py", ".txt", ".yaml", ".yml"}
+TEXT_SUFFIXES = {".cff", ".json", ".md", ".py", ".svg", ".txt", ".yaml", ".yml"}
EVIDENCE_CLASSES = {
"COMMITTED-RECORD",
"DESIGN-CONTRACT",
@@ -59,7 +66,48 @@
}
CLAIM_STATUSES = {"QUALIFIED", "SUPPORTED"}
PUBLICATION_STATUSES = {"DRAFT", "READY", "RELEASED", "SUPERSEDED"}
+VISUAL_APPROVAL_STATUSES = {"DRAFT", "OWNER_APPROVED", "SUPERSEDED"}
+INDEPENDENT_REVIEW_STATUSES = {"COMPLETED", "IN_PROGRESS", "NOT_PERFORMED"}
+SVG_NAMESPACE = "http://www.w3.org/2000/svg"
+STRUCTURED_CLAIM_SOURCES = {
+ "evidence/publication.json": {"ARC-001", "REL-003", "REV-001"},
+}
+PNG_ALLOWED_CHUNKS = {
+ b"IDAT",
+ b"IEND",
+ b"IHDR",
+ b"PLTE",
+ b"bKGD",
+ b"cHRM",
+ b"gAMA",
+ b"pHYs",
+ b"sBIT",
+ b"sRGB",
+ b"tRNS",
+}
+VISUAL_MANIFEST_KEYS = {
+ "independent_review_status",
+ "record_id",
+ "record_scope",
+ "release_boundary",
+ "schema_version",
+ "visuals",
+}
+VISUAL_ITEM_KEYS = {
+ "approval_status",
+ "claim_ids",
+ "description",
+ "does_not_establish",
+ "file",
+ "height",
+ "id",
+ "public_sources",
+ "title",
+ "view_box",
+ "width",
+}
CLAIM_MARKER = re.compile(r"")
+MARKDOWN_IMAGE = re.compile(r"!\[([^\]]*)\]\(([^)]+)\)")
PRIVATE_PATH = re.compile(
r"(?]+)", re.IGNORECASE
)
@@ -145,6 +193,7 @@ def verify_claims(root: Path, errors: list[str]) -> None:
return
ids: list[str] = []
+ sources_by_claim: dict[str, list[str]] = {}
for index, claim in enumerate(claims):
label = f"claim[{index}]"
if not isinstance(claim, dict):
@@ -158,21 +207,31 @@ def verify_claims(root: Path, errors: list[str]) -> None:
label = claim_id
if not isinstance(claim.get("statement"), str) or not claim["statement"].strip():
errors.append(f"{label} has no statement")
- if claim.get("evidence_class") not in EVIDENCE_CLASSES:
+ evidence_class = claim.get("evidence_class")
+ if not isinstance(evidence_class, str) or evidence_class not in EVIDENCE_CLASSES:
errors.append(f"{label} has an invalid evidence_class")
- if claim.get("status") not in CLAIM_STATUSES:
+ claim_status = claim.get("status")
+ if not isinstance(claim_status, str) or claim_status not in CLAIM_STATUSES:
errors.append(f"{label} has an invalid status")
sources = claim.get("public_sources")
if not isinstance(sources, list) or not sources:
errors.append(f"{label} has no public_sources")
+ sources = []
+ elif any(not isinstance(source, str) or not source for source in sources):
+ errors.append(f"{label} public_sources must contain only non-empty strings")
+ sources = [source for source in sources if isinstance(source, str) and source]
else:
if sources != sorted(set(sources)):
errors.append(f"{label} public_sources must be unique and sorted")
- for source in sources:
- if not isinstance(source, str) or not (root / source).is_file():
- errors.append(f"{label} references missing public source: {source!r}")
+ for source in sources:
+ if _repository_file(root, source) is None:
+ errors.append(f"{label} references missing public source: {source!r}")
+ if isinstance(claim_id, str):
+ sources_by_claim[claim_id] = sources
boundaries = claim.get("does_not_establish")
- if not isinstance(boundaries, list) or not boundaries:
+ if not isinstance(boundaries, list) or not boundaries or any(
+ not isinstance(boundary, str) or not boundary.strip() for boundary in boundaries
+ ):
errors.append(f"{label} must state at least one non-claim")
if ids != sorted(ids):
@@ -182,16 +241,19 @@ def verify_claims(root: Path, errors: list[str]) -> None:
registered = set(ids)
markers_by_source: dict[str, set[str]] = {}
- for source_path in sorted({source for claim in claims for source in claim.get("public_sources", [])}):
- if not isinstance(source_path, str) or not (root / source_path).is_file():
+ for source_path in sorted(
+ {source for claim_sources in sources_by_claim.values() for source in claim_sources}
+ ):
+ if _repository_file(root, source_path) is None:
continue
- text = (root / source_path).read_text(encoding="utf-8")
- markers: set[str] = set()
- for match in CLAIM_MARKER.finditer(text):
- marker_ids = match.group(1).split()
- if marker_ids != sorted(set(marker_ids)):
- errors.append(f"public claim marker must be unique and sorted: {source_path}")
- markers.update(marker_ids)
+ markers = set(STRUCTURED_CLAIM_SOURCES.get(source_path, set()))
+ if source_path not in STRUCTURED_CLAIM_SOURCES:
+ text = (root / source_path).read_text(encoding="utf-8")
+ for match in CLAIM_MARKER.finditer(text):
+ marker_ids = match.group(1).split()
+ if marker_ids != sorted(set(marker_ids)):
+ errors.append(f"public claim marker must be unique and sorted: {source_path}")
+ markers.update(marker_ids)
markers_by_source[source_path] = markers
for marker_id in sorted(markers - registered):
errors.append(f"unregistered public claim marker in {source_path}: {marker_id}")
@@ -200,8 +262,8 @@ def verify_claims(root: Path, errors: list[str]) -> None:
if not isinstance(claim, dict) or not isinstance(claim.get("id"), str):
continue
claim_id = claim["id"]
- for source_path in claim.get("public_sources", []):
- if isinstance(source_path, str) and claim_id not in markers_by_source.get(source_path, set()):
+ for source_path in sources_by_claim.get(claim_id, []):
+ if claim_id not in markers_by_source.get(source_path, set()):
errors.append(f"{claim_id} is not marked in declared public source: {source_path}")
@@ -217,23 +279,95 @@ def verify_publication_record(root: Path, errors: list[str], release: bool = Fal
if data.get("schema_version") != "1.0.0":
errors.append("publication schema_version must be 1.0.0")
- if data.get("status") not in PUBLICATION_STATUSES:
+ publication_status = data.get("status")
+ if not isinstance(publication_status, str) or publication_status not in PUBLICATION_STATUSES:
errors.append("publication status is invalid")
+ independent_review = data.get("independent_review")
+ if not isinstance(independent_review, dict):
+ errors.append("publication independent_review must be an object")
+ else:
+ review_status = independent_review.get("status")
+ if review_status not in INDEPENDENT_REVIEW_STATUSES:
+ errors.append("publication independent review status is invalid")
+ if review_status == "NOT_PERFORMED" and any(
+ independent_review.get(key) is not None
+ for key in ("public_statement", "reviewed_release")
+ ):
+ errors.append(
+ "NOT_PERFORMED independent review must not identify a review or statement"
+ )
+ expected_reviewed_release = f"v{data.get('version')}"
+ if review_status == "IN_PROGRESS":
+ if independent_review.get("reviewed_release") != expected_reviewed_release:
+ errors.append(
+ "IN_PROGRESS independent review must identify the version-bound release"
+ )
+ if independent_review.get("public_statement") is not None:
+ errors.append(
+ "IN_PROGRESS independent review must not identify a final public statement"
+ )
+ if review_status == "COMPLETED":
+ if independent_review.get("reviewed_release") != expected_reviewed_release:
+ errors.append(
+ "COMPLETED independent review must identify the version-bound release"
+ )
+ public_statement = independent_review.get("public_statement")
+ if not isinstance(public_statement, str) or not re.fullmatch(
+ r"https://[^\s]+", public_statement
+ ):
+ errors.append(
+ "COMPLETED independent review requires a stable HTTPS public statement"
+ )
+ if review_status in {"IN_PROGRESS", "COMPLETED"}:
+ claims_path = root / "evidence/public-claims.json"
+ if claims_path.is_file():
+ try:
+ claims_data = _load_json(claims_path)
+ except (OSError, ValueError, json.JSONDecodeError):
+ claims_data = {}
+ claims = claims_data.get("claims", [])
+ if isinstance(claims, list) and any(
+ isinstance(claim, dict) and claim.get("id") == "REV-001"
+ for claim in claims
+ ):
+ errors.append(
+ "active or completed independent review requires retiring "
+ "the REV-001 NOT_PERFORMED claim"
+ )
controls = data.get("publication_controls")
repository_approval = (
controls.get("repository_publication_approval")
if isinstance(controls, dict)
else None
)
- if data.get("status") in {"READY", "RELEASED"}:
+ if isinstance(publication_status, str) and publication_status in {"READY", "RELEASED"}:
if not isinstance(repository_approval, dict) or repository_approval.get("status") != "APPROVED":
errors.append("READY or RELEASED status requires repository publication approval")
- if isinstance(controls, dict) and controls.get("release_approval") not in {"PENDING", "APPROVED"}:
- errors.append("release_approval must be PENDING or APPROVED")
+ if isinstance(controls, dict):
+ release_approval = controls.get("release_approval")
+ if not isinstance(release_approval, str) or release_approval not in {
+ "APPROVED",
+ "PENDING",
+ }:
+ errors.append("release_approval must be PENDING or APPROVED")
observed_release = (
controls.get("observed_release") if isinstance(controls, dict) else None
)
- if data.get("status") == "RELEASED":
+ if publication_status == "RELEASED":
+ release_requirements = (
+ controls.get("release_requirements") if isinstance(controls, dict) else None
+ )
+ required_release_controls = {
+ "artifact_attestation",
+ "immutable_release",
+ "signed_annotated_tag",
+ "signed_commit",
+ }
+ if not isinstance(release_requirements, dict) or any(
+ release_requirements.get(key) is not True
+ for key in required_release_controls
+ ):
+ errors.append("RELEASED status requires all recorded release controls")
commit_sha = (
observed_release.get("commit_sha")
if isinstance(observed_release, dict)
@@ -288,7 +422,10 @@ def verify_publication_record(root: Path, errors: list[str], release: bool = Fal
if not isinstance(controls, dict) or controls.get("release_approval") != "APPROVED":
errors.append("RELEASED status requires explicit release approval")
if release:
- if data.get("status") not in {"READY", "RELEASED"}:
+ if not isinstance(publication_status, str) or publication_status not in {
+ "READY",
+ "RELEASED",
+ }:
errors.append("release verification requires publication status READY or RELEASED")
if not isinstance(controls, dict) or controls.get("release_approval") != "APPROVED":
errors.append("release verification requires explicit release approval")
@@ -336,6 +473,638 @@ def verify_publication_record(root: Path, errors: list[str], release: bool = Fal
pass
+def _xml_local_name(tag: str) -> str:
+ return tag.rsplit("}", 1)[-1]
+
+
+def _positive_integer(value: object) -> int | None:
+ if not isinstance(value, int) or isinstance(value, bool) or value <= 0:
+ return None
+ return value
+
+
+def _string_set(value: object) -> set[str] | None:
+ if not isinstance(value, list) or any(not isinstance(item, str) for item in value):
+ return None
+ return set(value)
+
+
+def _repository_file(root: Path, relative: object) -> Path | None:
+ if not isinstance(relative, str) or not relative or Path(relative).is_absolute():
+ return None
+ path = (root / relative).resolve()
+ try:
+ path.relative_to(root.resolve())
+ except ValueError:
+ return None
+ return path if path.is_file() else None
+
+
+def _verify_svg(
+ path: Path,
+ relative: str,
+ visual: dict,
+ errors: list[str],
+) -> None:
+ if path.stat().st_size > 250_000:
+ errors.append(f"SVG exceeds 250 KB visual limit: {relative}")
+ return
+ try:
+ source = path.read_text(encoding="utf-8")
+ except (OSError, UnicodeDecodeError) as exc:
+ errors.append(f"unreadable SVG visual {relative}: {exc}")
+ return
+ if re.search(r" None:
+ if path.stat().st_size > 5_000_000:
+ errors.append(f"PNG exceeds 5 MB visual limit: {relative}")
+ return
+ try:
+ data = path.read_bytes()
+ except OSError as exc:
+ errors.append(f"unreadable PNG visual {relative}: {exc}")
+ return
+ if len(data) < 33 or data[:8] != b"\x89PNG\r\n\x1a\n":
+ errors.append(f"invalid PNG signature: {relative}")
+ return
+
+ position = 8
+ chunk_index = 0
+ ihdr: bytes | None = None
+ idat_count = 0
+ iend_count = 0
+ while position < len(data):
+ if position + 12 > len(data):
+ errors.append(f"truncated PNG chunk: {relative}")
+ return
+ length = struct.unpack(">I", data[position : position + 4])[0]
+ chunk_type = data[position + 4 : position + 8]
+ chunk_end = position + 12 + length
+ if chunk_end > len(data):
+ errors.append(f"PNG chunk exceeds file bounds: {relative}")
+ return
+ chunk_data = data[position + 8 : position + 8 + length]
+ expected_crc = struct.unpack(">I", data[position + 8 + length : chunk_end])[0]
+ actual_crc = zlib.crc32(chunk_type)
+ actual_crc = zlib.crc32(chunk_data, actual_crc) & 0xFFFFFFFF
+ if actual_crc != expected_crc:
+ errors.append(f"PNG chunk CRC mismatch: {relative}")
+ if not re.fullmatch(rb"[A-Za-z]{4}", chunk_type):
+ errors.append(f"PNG chunk type is invalid: {relative}")
+ if chunk_type not in PNG_ALLOWED_CHUNKS:
+ errors.append(f"PNG contains unsupported metadata or chunk type: {relative}")
+ if chunk_index == 0 and (chunk_type != b"IHDR" or length != 13):
+ errors.append(f"PNG must begin with a 13-byte IHDR chunk: {relative}")
+ if chunk_type == b"IHDR":
+ if ihdr is not None or length != 13:
+ errors.append(f"PNG must contain exactly one valid IHDR chunk: {relative}")
+ else:
+ ihdr = chunk_data
+ elif chunk_type == b"IDAT":
+ idat_count += 1
+ elif chunk_type == b"IEND":
+ iend_count += 1
+ if length != 0:
+ errors.append(f"PNG IEND chunk must be empty: {relative}")
+ if chunk_end != len(data):
+ errors.append(f"PNG must end exactly at its IEND chunk: {relative}")
+ return
+ position = chunk_end
+ chunk_index += 1
+
+ if ihdr is None or idat_count == 0 or iend_count != 1:
+ errors.append(f"PNG must contain IHDR, IDAT, and exactly one IEND: {relative}")
+ return
+ width, height, bit_depth, color_type, compression, filtering, interlace = struct.unpack(
+ ">IIBBBBB", ihdr
+ )
+ valid_depths = {
+ 0: {1, 2, 4, 8, 16},
+ 2: {8, 16},
+ 3: {1, 2, 4, 8},
+ 4: {8, 16},
+ 6: {8, 16},
+ }
+ if (
+ color_type not in valid_depths
+ or bit_depth not in valid_depths[color_type]
+ or compression != 0
+ or filtering != 0
+ or interlace not in {0, 1}
+ ):
+ errors.append(f"PNG IHDR contains unsupported values: {relative}")
+ if width != visual.get("width") or height != visual.get("height"):
+ errors.append(f"PNG dimensions differ from visual manifest: {relative}")
+ if visual.get("view_box") is not None:
+ errors.append(f"PNG visual view_box must be null: {relative}")
+
+
+def verify_visual_schema(root: Path, errors: list[str]) -> None:
+ path = root / "schemas/visual-manifest.schema.json"
+ if not path.is_file():
+ return
+ try:
+ schema = _load_json(path)
+ except (OSError, ValueError, json.JSONDecodeError) as exc:
+ errors.append(f"invalid visual manifest schema JSON: {exc}")
+ return
+ properties = schema.get("properties")
+ if (
+ schema.get("$schema") != "https://json-schema.org/draft/2020-12/schema"
+ or schema.get("type") != "object"
+ or schema.get("additionalProperties") is not False
+ or _string_set(schema.get("required")) != VISUAL_MANIFEST_KEYS
+ or not isinstance(properties, dict)
+ or set(properties) != VISUAL_MANIFEST_KEYS
+ ):
+ errors.append("visual manifest schema root contract is inconsistent")
+ return
+ version_schema = properties.get("schema_version")
+ version_schema = version_schema if isinstance(version_schema, dict) else {}
+ if version_schema.get("const") != "1.0.0":
+ errors.append("visual manifest schema version contract is inconsistent")
+ scope_schema = properties.get("record_scope")
+ scope_schema = scope_schema if isinstance(scope_schema, dict) else {}
+ if (
+ scope_schema.get("const")
+ != "POST_RELEASE_DOCUMENTATION_REFINEMENT"
+ ):
+ errors.append("visual manifest schema scope contract is inconsistent")
+ review_schema = properties.get("independent_review_status")
+ review_schema = review_schema if isinstance(review_schema, dict) else {}
+ if _string_set(review_schema.get("enum")) != (
+ INDEPENDENT_REVIEW_STATUSES
+ ):
+ errors.append("visual manifest schema review-status contract is inconsistent")
+ visuals_schema = properties.get("visuals")
+ visuals_schema = visuals_schema if isinstance(visuals_schema, dict) else {}
+ item_schema = visuals_schema.get("items")
+ item_schema = item_schema if isinstance(item_schema, dict) else {}
+ item_properties = item_schema.get("properties")
+ if (
+ visuals_schema.get("type") != "array"
+ or visuals_schema.get("minItems") != 1
+ or item_schema.get("type") != "object"
+ or item_schema.get("additionalProperties") is not False
+ or _string_set(item_schema.get("required")) != VISUAL_ITEM_KEYS
+ or not isinstance(item_properties, dict)
+ or set(item_properties) != VISUAL_ITEM_KEYS
+ ):
+ errors.append("visual manifest schema item contract is inconsistent")
+ return
+ approval_schema = item_properties.get("approval_status")
+ approval_schema = approval_schema if isinstance(approval_schema, dict) else {}
+ if _string_set(approval_schema.get("enum")) != (
+ VISUAL_APPROVAL_STATUSES
+ ):
+ errors.append("visual manifest schema approval-status contract is inconsistent")
+
+
+def verify_visual_manifest(root: Path, errors: list[str]) -> None:
+ manifest_path = root / "assets/visuals/manifest.json"
+ claims_path = root / "evidence/public-claims.json"
+ publication_path = root / "evidence/publication.json"
+ if not manifest_path.is_file() or not claims_path.is_file():
+ return
+ verify_visual_schema(root, errors)
+ try:
+ manifest = _load_json(manifest_path)
+ except (OSError, ValueError, json.JSONDecodeError) as exc:
+ errors.append(f"invalid visual manifest JSON: {exc}")
+ return
+ try:
+ claims_data = _load_json(claims_path)
+ except (OSError, ValueError, json.JSONDecodeError):
+ return
+
+ missing_manifest_keys = sorted(VISUAL_MANIFEST_KEYS - set(manifest))
+ extra_manifest_keys = sorted(set(manifest) - VISUAL_MANIFEST_KEYS)
+ for key in missing_manifest_keys:
+ errors.append(f"visual manifest is missing required field: {key}")
+ for key in extra_manifest_keys:
+ errors.append(f"visual manifest contains unsupported field: {key}")
+ if manifest.get("schema_version") != "1.0.0":
+ errors.append("visual manifest schema_version must be 1.0.0")
+ if not isinstance(manifest.get("record_id"), str) or not manifest["record_id"].strip():
+ errors.append("visual manifest record_id must be a non-empty string")
+ if manifest.get("record_scope") != "POST_RELEASE_DOCUMENTATION_REFINEMENT":
+ errors.append("visual manifest record_scope is invalid")
+ if not isinstance(manifest.get("release_boundary"), str) or not manifest[
+ "release_boundary"
+ ].strip():
+ errors.append("visual manifest must state its release boundary")
+ manifest_review_status = manifest.get("independent_review_status")
+ if (
+ not isinstance(manifest_review_status, str)
+ or manifest_review_status not in INDEPENDENT_REVIEW_STATUSES
+ ):
+ errors.append("visual manifest independent_review_status is invalid")
+
+ if publication_path.is_file():
+ try:
+ publication = _load_json(publication_path)
+ except (OSError, ValueError, json.JSONDecodeError):
+ publication = {}
+ independent_review = publication.get("independent_review", {})
+ expected_status = (
+ independent_review.get("status")
+ if isinstance(independent_review, dict)
+ else None
+ )
+ if manifest_review_status != expected_status:
+ errors.append(
+ "visual manifest independent_review_status differs from publication record"
+ )
+
+ claims = claims_data.get("claims", [])
+ if not isinstance(claims, list):
+ claims = []
+ claim_map = {
+ claim.get("id"): claim
+ for claim in claims
+ if isinstance(claim, dict) and isinstance(claim.get("id"), str)
+ }
+ claim_source_map: dict[str, set[str]] = {}
+ for claim_id, claim in claim_map.items():
+ raw_sources = claim.get("public_sources")
+ claim_source_map[claim_id] = (
+ {
+ source
+ for source in raw_sources
+ if isinstance(source, str) and source
+ }
+ if isinstance(raw_sources, list)
+ else set()
+ )
+ visuals = manifest.get("visuals")
+ if not isinstance(visuals, list) or not visuals:
+ errors.append("visual manifest visuals must be a non-empty list")
+ return
+
+ visual_ids: list[str] = []
+ registered_files: list[str] = []
+ for index, visual in enumerate(visuals):
+ label = f"visual[{index}]"
+ if not isinstance(visual, dict):
+ errors.append(f"{label} must be an object")
+ continue
+ missing_visual_keys = sorted(VISUAL_ITEM_KEYS - set(visual))
+ extra_visual_keys = sorted(set(visual) - VISUAL_ITEM_KEYS)
+ for key in missing_visual_keys:
+ errors.append(f"{label} is missing required field: {key}")
+ for key in extra_visual_keys:
+ errors.append(f"{label} contains unsupported field: {key}")
+ visual_id = visual.get("id")
+ if not isinstance(visual_id, str) or not re.fullmatch(
+ r"[a-z0-9]+(?:-[a-z0-9]+)*", visual_id
+ ):
+ errors.append(f"{label} has an invalid id")
+ else:
+ visual_ids.append(visual_id)
+ label = visual_id
+ relative = visual.get("file")
+ if not isinstance(relative, str) or not re.fullmatch(
+ r"assets/visuals/[a-z0-9-]+\.(?:svg|png)", relative
+ ):
+ errors.append(f"{label} has an invalid visual file path")
+ continue
+ registered_files.append(relative)
+ path = (root / relative).resolve()
+ try:
+ path.relative_to((root / "assets/visuals").resolve())
+ except ValueError:
+ errors.append(f"{label} visual file escapes assets/visuals")
+ continue
+ if not path.is_file():
+ errors.append(f"{label} references missing visual file: {relative}")
+ continue
+
+ for key in ("title", "description"):
+ if not isinstance(visual.get(key), str) or not visual[key].strip():
+ errors.append(f"{label} has no {key}")
+ width = _positive_integer(visual.get("width"))
+ height = _positive_integer(visual.get("height"))
+ if width is None or height is None:
+ errors.append(f"{label} width and height must be positive integers")
+ view_box = visual.get("view_box")
+ if view_box is not None and (
+ not isinstance(view_box, str)
+ or not re.fullmatch(r"0 0 [1-9][0-9]* [1-9][0-9]*", view_box)
+ ):
+ errors.append(f"{label} has an invalid view_box")
+
+ claim_ids = visual.get("claim_ids")
+ if not isinstance(claim_ids, list) or not claim_ids:
+ errors.append(f"{label} must declare at least one claim_id")
+ claim_ids = []
+ elif any(
+ not isinstance(claim_id, str)
+ or not re.fullmatch(r"[A-Z]{3}-[0-9]{3}", claim_id)
+ for claim_id in claim_ids
+ ):
+ errors.append(f"{label} claim_ids must contain only valid strings")
+ claim_ids = [claim_id for claim_id in claim_ids if isinstance(claim_id, str)]
+ elif claim_ids != sorted(set(claim_ids)):
+ errors.append(f"{label} claim_ids must be unique and sorted")
+ sources = visual.get("public_sources")
+ if not isinstance(sources, list) or not sources:
+ errors.append(f"{label} must declare at least one public source")
+ sources = []
+ elif any(not isinstance(source, str) or not source for source in sources):
+ errors.append(f"{label} public_sources must contain only non-empty strings")
+ sources = [source for source in sources if isinstance(source, str) and source]
+ elif sources != sorted(set(sources)):
+ errors.append(f"{label} public_sources must be unique and sorted")
+ valid_sources = {
+ source for source in sources if _repository_file(root, source) is not None
+ }
+ for source in sources:
+ if _repository_file(root, source) is None:
+ errors.append(f"{label} references missing public source: {source!r}")
+ for claim_id in claim_ids:
+ claim = claim_map.get(claim_id)
+ if claim is None:
+ errors.append(f"{label} references unknown public claim: {claim_id!r}")
+ continue
+ claim_sources = claim_source_map.get(claim_id, set())
+ if not claim_sources.intersection(valid_sources):
+ errors.append(
+ f"{label} claim {claim_id} is unsupported by its declared public_sources"
+ )
+ referenced_claims = [
+ claim_map[claim_id] for claim_id in claim_ids if claim_id in claim_map
+ ]
+ for source in sorted(valid_sources):
+ if not any(
+ source in claim_source_map.get(claim.get("id"), set())
+ for claim in referenced_claims
+ ):
+ errors.append(
+ f"{label} source is unrelated to its referenced claims: {source}"
+ )
+ boundaries = visual.get("does_not_establish")
+ if not isinstance(boundaries, list) or not boundaries or any(
+ not isinstance(item, str) or not item.strip() for item in boundaries
+ ):
+ errors.append(f"{label} must state at least one visual non-claim")
+ approval_status = visual.get("approval_status")
+ if (
+ not isinstance(approval_status, str)
+ or approval_status not in VISUAL_APPROVAL_STATUSES
+ ):
+ errors.append(f"{label} has an invalid approval_status")
+ if manifest_review_status != "NOT_PERFORMED" and "REV-001" in claim_ids:
+ errors.append(
+ f"{label} retains the REV-001 NOT_PERFORMED claim while independent "
+ "review status has advanced"
+ )
+
+ if path.suffix.lower() == ".svg":
+ _verify_svg(path, relative, visual, errors)
+ else:
+ _verify_png(path, relative, visual, errors)
+
+ if visual_ids != sorted(visual_ids):
+ errors.append("visual manifest entries must be sorted by id")
+ if len(visual_ids) != len(set(visual_ids)):
+ errors.append("visual manifest ids must be unique")
+ if len(registered_files) != len(set(registered_files)):
+ errors.append("visual manifest files must be unique")
+
+ actual_files = {
+ _relative(root, path)
+ for path in (root / "assets/visuals").rglob("*")
+ if path.is_file() and path.suffix.lower() in {".png", ".svg"}
+ }
+ registered_file_set = set(registered_files)
+ for relative in sorted(actual_files - registered_file_set):
+ errors.append(f"unregistered visual asset: {relative}")
+ for relative in sorted(registered_file_set - actual_files):
+ errors.append(f"registered visual asset is missing: {relative}")
+
+ approval_by_file = {
+ visual.get("file"): visual.get("approval_status")
+ for visual in visuals
+ if isinstance(visual, dict) and isinstance(visual.get("file"), str)
+ }
+ for markdown_path in _iter_text_files(root):
+ if markdown_path.suffix.lower() != ".md":
+ continue
+ text = markdown_path.read_text(encoding="utf-8")
+ if re.search(r"!\[[^\]]*\]\[[^\]]*\]", text):
+ errors.append(
+ f"reference-style Markdown images are not permitted in {_relative(root, markdown_path)}"
+ )
+ if re.search(r"<(?:img|picture|source)\b", text, flags=re.IGNORECASE):
+ errors.append(
+ f"raw HTML image elements are not permitted in {_relative(root, markdown_path)}"
+ )
+ for alt_text, raw_target in MARKDOWN_IMAGE.findall(text):
+ target = raw_target.strip().split(maxsplit=1)[0].strip("<>")
+ target = target.split("#", 1)[0]
+ if target.startswith(("http://", "https://")):
+ continue
+ if not alt_text.strip():
+ errors.append(
+ f"local Markdown image has empty alt text in {_relative(root, markdown_path)}"
+ )
+ resolved = (markdown_path.parent / target).resolve()
+ try:
+ relative = _relative(root, resolved)
+ except ValueError:
+ errors.append(
+ f"image link escapes repository in {_relative(root, markdown_path)}: {target}"
+ )
+ continue
+ if not resolved.is_file():
+ errors.append(
+ f"broken local image in {_relative(root, markdown_path)}: {target}"
+ )
+ elif relative not in registered_file_set:
+ errors.append(
+ f"Markdown image is not registered in visual manifest: {relative}"
+ )
+ elif approval_by_file.get(relative) == "SUPERSEDED":
+ errors.append(f"Markdown embeds superseded visual asset: {relative}")
+
+
+def _iter_json_strings(value: object, path: tuple[str, ...] = ()):
+ if isinstance(value, dict):
+ for key, child in value.items():
+ if isinstance(key, str):
+ yield path + (key,), key
+ yield from _iter_json_strings(child, path + (key,))
+ elif isinstance(value, list):
+ for index, child in enumerate(value):
+ yield from _iter_json_strings(child, path + (str(index),))
+ elif isinstance(value, str):
+ yield path, value
+
+
+def _decoded_sensitive_text(relative: str, raw_text: str) -> str:
+ fragments = [html.unescape(raw_text)]
+ if relative.endswith(".json"):
+ try:
+ parsed = json.loads(raw_text)
+ except json.JSONDecodeError:
+ parsed = None
+ if parsed is not None:
+ exempt_path = (
+ "publication_controls",
+ "observed_release",
+ "commit_sha",
+ )
+ for path, value in _iter_json_strings(parsed):
+ if relative == "evidence/publication.json" and path == exempt_path:
+ continue
+ fragments.append(html.unescape(value))
+ elif relative.endswith(".svg"):
+ try:
+ svg = ET.fromstring(raw_text)
+ except ET.ParseError:
+ svg = None
+ if svg is not None:
+ for element in svg.iter():
+ if element.text:
+ fragments.append(html.unescape(element.text))
+ if element.tail:
+ fragments.append(html.unescape(element.tail))
+ fragments.extend(html.unescape(value) for value in element.attrib.values())
+ return "\n".join(fragments)
+
+
def verify_sensitive_content(
root: Path,
errors: list[str],
@@ -358,18 +1127,6 @@ def verify_sensitive_content(
errors.append(f"non-UTF-8 text file: {relative}")
continue
- lowered = text.casefold()
- for literal in private_deny_list:
- if literal.casefold() in lowered:
- errors.append(f"external private deny-list match in {relative}")
- if disallowed_phrase in lowered:
- errors.append(f"disallowed positioning phrase in {relative}")
- if PRIVATE_PATH.search(text):
- errors.append(f"operational-style absolute path found in {relative}")
- for repository in GITHUB_REPOSITORY_URL.findall(text):
- if repository.casefold() != CANONICAL_REPOSITORY.casefold():
- errors.append(f"non-canonical GitHub repository URL found in {relative}")
-
# Workflow actions are intentionally pinned to full upstream SHAs. The
# one other permitted full SHA is the structured, public release commit
# recorded after publication. Redact only that exact JSON field; any
@@ -395,13 +1152,28 @@ def verify_sensitive_content(
r"\1\2", sha_scan_text, count=1
)
+ scan_text = _decoded_sensitive_text(relative, sha_scan_text)
+ lowered = scan_text.casefold()
+ for literal in private_deny_list:
+ if literal.casefold() in lowered:
+ errors.append(f"external private deny-list match in {relative}")
+ if disallowed_phrase in lowered:
+ errors.append(f"disallowed positioning phrase in {relative}")
+ if chr(0x2014) in scan_text:
+ errors.append(f"prohibited em dash character in {relative}")
+ if PRIVATE_PATH.search(scan_text):
+ errors.append(f"operational-style absolute path found in {relative}")
+ for repository in GITHUB_REPOSITORY_URL.findall(scan_text):
+ if repository.casefold() != CANONICAL_REPOSITORY.casefold():
+ errors.append(f"non-canonical GitHub repository URL found in {relative}")
+
workflow_source = relative.startswith(".github/workflows/")
- if not workflow_source and full_sha.search(sha_scan_text):
+ if not workflow_source and full_sha.search(scan_text):
errors.append(f"full private-style Git SHA found in {relative}")
- if internal_id.search(text):
+ if internal_id.search(scan_text):
errors.append(f"private workflow identifier found in {relative}")
for pattern in secret_patterns:
- if pattern.search(text):
+ if pattern.search(scan_text):
errors.append(f"possible secret material found in {relative}")
@@ -470,6 +1242,7 @@ def verify(
verify_required_files(root, errors)
verify_claims(root, errors)
verify_publication_record(root, errors, release=release)
+ verify_visual_manifest(root, errors)
try:
private_literals = _load_private_deny_list(private_deny_list)
except (OSError, ValueError) as exc:
@@ -508,6 +1281,7 @@ def main() -> int:
print("PUBLICATION VERIFICATION: PASSED")
print("- required structure present")
print("- public claim register and source markers consistent")
+ print("- visual manifest, assets, and accessibility contract consistent")
print("- private locator and secret scan passed")
print("- local Markdown links resolved")
print("- citation metadata present")