1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Compare commits

..

3 commits
v1.4.0 ... main

23 changed files with 419 additions and 1307 deletions

View file

@ -1,134 +1,57 @@
# Milestone state # Active slice
```text ```text
Last completed milestone: 5 stabilization and first DocForge2 release. Last completed milestone: 5 - stabilization and first DocForge2 release.
Baseline: annotated v1.4.0 release commit on main, dev, origin/main, and origin/dev. Release: 2.0.0.
Active milestone: none. Active milestone: none.
Outcome: Compatibility, determinism, recovery, security, performance, and representative task advantage are proven for the first successor release. Maintenance: DocForge2 version identity and cross-identity proposal acceptance released.
Status: Complete. Status: Complete.
Release: 1.4.0.
``` ```
## Authority ## Current state
This contract activated Milestone 5 from the clean, merged, and pushed Milestone 4 closeout. The DocForge2 `2.0.0` is the released product baseline. Current behavior is defined by the contracts
roadmap in `/home/andraxion/.openclaw/workspace/DocForgeOutline.md` supplies direction; this file and guides under `docs/`. `SLICE_HISTORY.md` contains DocForge2 milestone summaries only.
freezes the executable scope and acceptance criteria.
The completed release synchronizes `main` and `dev` at the documentation-bearing commit identified The release adds an explicit accepted-writer allowlist for project-owned canonical appliers.
by annotated tag `v1.4.0`. Same-identity application remains the default. Contributor processes receive no application tool,
and successful receipts record both the proposal creator and applier.
## Required release evidence The live documentation set contains current product contracts, current operating guidance, and
current successor milestone evidence. Superseded product plans and policy notes remain available
only through repository history and are not part of normal documentation validation or retrieval.
The release candidate must prove all of the following from maintained, reproducible gates: ## Maintenance proof - 2026-07-31
1. Legacy one-method adapter compatibility and the frozen package, CLI, MCP, schema, rendering, - Replaced the historical application-decision memo with the current canonical-application
descriptor, result-envelope, and no-AST surfaces. contract.
2. Exact complete/incremental primary-graph and Logic equivalence for maintained incremental - Removed the predecessor chronology from the live slice history and removed the transition-only
adapters. repository closeout page.
3. Deterministic adapter, worker, renderer, command-reference, configuration, and publication - Updated the README and documentation validator to reference only current product documentation.
output. - Formatting, Python lint, web lint, command-reference validation, and the 33-page documentation
4. Detection of concurrent source mutation without publishing or serving a mixed or stale graph passed.
generation. - Focused adapter and changeset verification passed 45 tests plus 2 subtests.
5. Atomic, crash-safe derived publication and exact recovery from interrupted publication.
6. Corrupt extraction-cache, index, attestation, receipt, fragment, and projection recovery using
canonical sources as the authority.
7. No stale reads after source or policy change, including generation-pinned retrieval and viewer
behavior.
8. Exact-hash canonical application with project-owned serialization, stale-proposal rejection,
and post-application resynchronization.
9. Closed policy precedence across process capability, descriptor policy, `--no-ast`, projection
policy, worker enforcement, and viewer enforcement.
10. Manual and portable-graph isolation, immutable package verification, bounded detached workers,
rendering-policy enforcement, viewer-policy enforcement, and accessibility.
11. Comparative representative task evidence with fixed questions, answer keys, provenance,
latency, and response-size accounting for both graph-assisted and source-only workflows.
12. A fresh-wheel and fresh-clone release rehearsal, full quality and browser gates, maintained
benchmarks, secret scans, exact version identity, signed-off release notes, and reproducible
release artifacts.
## Deliverables ## Maintenance proof - 2026-08-02
- A maintained Milestone 5 compatibility matrix and aggregate release gate. - Compared exact released commit `f9a05f868eec6c35e2b74a69467459e6ff2190bf` with exact development
- Reproducible migration, recovery, concurrency, and comparative-task evidence. commit `7b21541ab37c35dc9f3d47f631fef0c1884f9525` in isolated locked environments.
- One authoritative package version shared by package metadata, Python, CLI, MCP, viewer manager, - The development repository gate passed formatting, Ruff, web lint, strict Pyright, compilation,
reference MCP, generated configuration, and release documentation. 142 contract tests plus 272 subtests, 381 complete tests plus 422 subtests, three accessibility
- A standard project license file, changelog or release notes, release baseline, closeout record, flows, dependency, build, documentation, and five smoke benchmark gates.
and machine-readable evidence. - Compatibility passed 116 tests plus 263 subtests, concurrency and application passed 31 tests
- A clean `main` merge, synchronized `main` and `dev`, annotated `v1.4.0` tag, and Forgejo release plus 2 subtests, recovery passed 72 tests plus 62 subtests, offline fresh-wheel adoption passed,
only after the final documentation-bearing commit passes a fresh-clone gate. reproducible artifact checks passed, and Git plus directory secret scans found no leaks.
- All six full maintained benchmark tracks passed. Across 66 multi-sample operations, the median
absolute development-versus-release difference was 0.71%; 65 remained within 5%, and the only
larger difference was 0.062 ms on a 1.156 ms viewer-overview operation.
- Reference-adapter graph and Logic evidence and representative-task semantic evidence matched the
release exactly. All six representative tasks retained exact answers.
- Anonymous exact-commit fresh clones of both revisions completed the full release gate offline and
remained clean. The development rehearsal completed in 283.490 seconds versus 282.091 seconds for
the release.
## Fixed boundaries ## Next gate
- Preserve the `docforge` package, `docforge` CLI, `docforge-mcp`, MCP tool names, schema version 1 No implementation milestone is active. Any additional product behavior, version identity change,
surfaces, effective policy version 1, projection policy version 2, and legacy adapter entry point. release, or public integration requires its own bounded contract.
- Preserve canonical project sources. Migration may rebuild disposable state but may not rewrite
canonical content merely to satisfy the release.
- Full rebuild remains the recovery and equivalence oracle.
- Derived artifacts must fail closed on malformed, foreign, stale, oversized, or incompatible
state.
- Version `1.4.0` is additive relative to `1.0.0`; breaking a frozen contract requires a separately
justified major-version decision.
## Exclusions
- No WorldForge change or benchmark.
- No ScrapeStation change, production binding change, or production migration.
- No legacy-repository mutation.
- No production MCP repointing.
- No remote render service, render farm, third-party renderer ecosystem, graph federation, or
dedicated graph database.
- No arbitrary adapter command execution, compiler execution, remote execution, or expanded
launcher authority.
- No PyPI publication unless it is separately verified as an intended existing release channel.
## Release sequence
1. Freeze and implement the compatibility, migration, recovery, concurrency, and task-evidence
gates.
2. Stabilize version identity, packaging, license, security, and release automation.
3. Freeze one clean executable candidate and run the full repository, browser, benchmark,
fresh-wheel, fresh-clone, and secret-scan gates.
4. Close documentation atomically against that candidate and rerun documentation-only validation.
5. Merge and push the final candidate.
6. Rehearse from a fresh anonymous clone at the exact commit.
7. Create and push the annotated `v1.4.0` tag and publish the Forgejo release from the verified
artifacts.
The completed Milestone 4 contract and exclusions remain preserved in `SLICE_HISTORY.md`.
## Release-candidate evidence — 2026-07-29
The executable implementation is frozen at
`d2bb95fe6190e659cf66ba57c78be53b63b53240`. The proof-bearing candidate base is
`2b98059b44f4d46b4d4cce776f163e893c647c76`; it includes exact legacy-tag verification for the
fresh-clone gate and four maintained aggregate derived-recovery tests.
The clean executable release gate passed formatting, Python and web lint, strict Pyright,
compilation, lock and dependency checks, builds, generated documentation, three accessibility
flows, fresh-wheel adoption, artifact reproducibility, secret scans, and the full Milestone 0
through Milestone 4 benchmark sequence. Its exact test evidence was:
- 142 contract tests plus 272 subtests.
- 371 complete tests plus 419 subtests.
- 116 compatibility tests plus 263 subtests.
- 29 concurrency tests plus 2 subtests.
- 68 recovery tests plus 62 subtests.
The real migration gate preserves exact canonical and proposal bytes from the annotated `v1.0.0`
lineage while rebuilding the disposable index from schema 1 to schema 3. The real-package task
gate uses the lock-pinned `markdown-it-py 4.2.0` tree and proves exact graph-assisted and
source-only answers for all reviewed tasks.
The later proof-only recovery commit adds four maintained tests without changing executable
product code. The current recovery aggregate passes 72 tests plus 62 subtests.
The documentation-bearing candidate `49e1a87c138cdc63fb5abb85fc6eb2cf9f4a9d73` passed the complete
local release gate with 378 tests plus 422 subtests, all three accessibility flows, and every
focused and full gate above. Its final documentation-only descendant is the commit identified by
annotated tag `v1.4.0`; that exact remote commit passes the anonymous fresh-clone rehearsal before
tagging.
The public Forgejo release publishes the reproducible wheel, source distribution, and
machine-readable release-identity evidence from the tagged commit. `main`, `dev`, `origin/main`,
and `origin/dev` resolve to that same commit. No PyPI publication was performed.

View file

@ -5,6 +5,17 @@ remains in `docs/MILESTONE_*_BASELINE.md` and `docs/MILESTONE_*_CLOSEOUT.md`.
## Unreleased ## Unreleased
## 2.0.0 - 2026-08-02
- Established `2.0.0` as the unambiguous package, Python, CLI, MCP, viewer-manager, and generated
client identity for DocForge2 without changing descriptor, result, adapter, or index schemas.
- Project-owned MCP servers can explicitly authorize a canonical applier to accept exact-hash
changesets from additional configured proposal writers. The default remains same-identity
application, contributor processes receive no application tool, and application receipts record
both the proposal creator and applier.
- Retained the full DocForge 1.4 compatibility, recovery, migration, benchmark, and reproducible
artifact gates.
## 1.4.0 - 2026-07-29 ## 1.4.0 - 2026-07-29
Version `1.4.0` is the first additive DocForge2 successor release. Annotated tag `v1.4.0` Version `1.4.0` is the first additive DocForge2 successor release. Annotated tag `v1.4.0`

View file

@ -9,9 +9,10 @@ DocForge never treats indexed text as instructions. It does not run project buil
compilers, Git operations, deployments, or arbitrary renderers, and it does not select projects compilers, Git operations, deployments, or arbitrary renderers, and it does not select projects
globally. globally.
DocForge 1.4.0 is the first stable DocForge2 successor release. It contains the complete adapter DocForge 2.0.0 is the current stable DocForge2 release. It gives the successor product an
platform, stabilization and recovery evidence, representative real-task proof, and reproducible unambiguous major-version identity, preserves the complete 1.4 adapter and recovery platform, and
release artifacts. Annotated tag `v1.4.0` identifies the synchronized release commit. adds explicit cross-identity proposal acceptance for project-owned canonical appliers. Annotated
tag `v2.0.0` identifies the synchronized release commit.
## Start here ## Start here
@ -204,7 +205,7 @@ paths.
- [Compatibility contract](docs/COMPATIBILITY.md) - [Compatibility contract](docs/COMPATIBILITY.md)
- [Adapter authoring guide](docs/ADAPTER_AUTHORING_GUIDE.md) - [Adapter authoring guide](docs/ADAPTER_AUTHORING_GUIDE.md)
- [Incremental indexing](docs/INCREMENTAL_INDEXING.md) - [Incremental indexing](docs/INCREMENTAL_INDEXING.md)
- [Adapter application decision](docs/APPLICATION_DECISION.md) - [Canonical application](docs/CANONICAL_APPLICATION.md)
- [Legacy and no-AST operation](docs/LEGACY_AND_NO_AST.md) - [Legacy and no-AST operation](docs/LEGACY_AND_NO_AST.md)
- [Migrating from version 1](docs/MIGRATING_FROM_V1.md) - [Migrating from version 1](docs/MIGRATING_FROM_V1.md)
- [Viewer manager](docs/VIEWER_MANAGER.md) - [Viewer manager](docs/VIEWER_MANAGER.md)
@ -218,7 +219,7 @@ paths.
- [Milestone 3 baseline](docs/MILESTONE_3_BASELINE.md) and [closeout](docs/MILESTONE_3_CLOSEOUT.md) - [Milestone 3 baseline](docs/MILESTONE_3_BASELINE.md) and [closeout](docs/MILESTONE_3_CLOSEOUT.md)
- [Milestone 2 baseline](docs/MILESTONE_2_BASELINE.md) and [closeout](docs/MILESTONE_2_CLOSEOUT.md) - [Milestone 2 baseline](docs/MILESTONE_2_BASELINE.md) and [closeout](docs/MILESTONE_2_CLOSEOUT.md)
- [Milestone 1 baseline](docs/MILESTONE_1_BASELINE.md) and [closeout](docs/MILESTONE_1_CLOSEOUT.md) - [Milestone 1 baseline](docs/MILESTONE_1_BASELINE.md) and [closeout](docs/MILESTONE_1_CLOSEOUT.md)
- [Milestone 0 baseline](docs/MILESTONE_0_BASELINE.md) and [closeout](docs/MILESTONE_0_CLOSEOUT.md) - [Milestone 0 baseline](docs/MILESTONE_0_BASELINE.md)
Historical milestone records preserve the facts and dependency observations of their frozen Historical milestone records preserve the facts and dependency observations of their frozen
candidates. Use the current guides and contracts for present behavior. candidates. Use the current guides and contracts for present behavior.

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,4 @@
# Canonical application decision # Canonical application
**Status:** Superseded by the DocForge 0.13 hash-bound application contract.
## Decision
DocForge may apply one isolated changeset to canonical project sources through an explicit, DocForge may apply one isolated changeset to canonical project sources through an explicit,
project-bound canonical applier. Application is available through both CLI and MCP. It is never an project-bound canonical applier. Application is available through both CLI and MCP. It is never an
@ -17,7 +13,11 @@ files.
- CLI requires `apply CHANGESET_ID --changeset-hash SHA256 --applier WRITER_ID`. - CLI requires `apply CHANGESET_ID --changeset-hash SHA256 --applier WRITER_ID`.
- MCP registers `docforge_apply_changeset` only when the server starts with an explicit canonical - MCP registers `docforge_apply_changeset` only when the server starts with an explicit canonical
applier identity and compatible applier implementation. applier identity and compatible applier implementation.
- The changeset creator and applier identity must match a configured proposal writer. - The changeset creator must be a configured proposal writer.
- Application defaults to changesets created by the applier identity. A project-owned server may
explicitly bind additional configured proposal writers that its applier is authorized to accept.
- Cross-identity acceptance does not let the applier edit the contributor's proposal and does not
give the contributor an application tool.
- The exact final changeset hash is required. Any proposal mutation invalidates an earlier - The exact final changeset hash is required. Any proposal mutation invalidates an earlier
approval. approval.
@ -31,17 +31,15 @@ checks the derived index and regenerates declared render views.
Application does not run project commands, tests, shell operations, Git, deployment, publication, Application does not run project commands, tests, shell operations, Git, deployment, publication,
or arbitrary renderers. Those remain with the owning project workflow. or arbitrary renderers. Those remain with the owning project workflow.
## Why the earlier decision changed ## Safety contract
The earlier DFG-9 decision preserved manual integration because there was not yet repeated evidence The application boundary requires:
for canonical application. Later multi-project use produced recurring proposal application work,
stale-index round trips, and an explicit user requirement for faster approved integration. The new
contract addresses the original safety concerns with:
- exact changeset-hash approval; - exact changeset-hash approval;
- startup-bound applier identity; - startup-bound applier identity;
- an explicit accepted-writer allowlist for any cross-identity application;
- project-owned serializers for custom adapters; - project-owned serializers for custom adapters;
- canonical path and symlink confinement; - canonical path and symbolic-link confinement;
- rollback and semantic round-trip verification; - rollback and semantic round-trip verification;
- deterministic derived-state refresh; and - deterministic derived-state refresh; and
- complete separation from Git, builds, deployment, and publication. - complete separation from Git, builds, deployment, and publication.

View file

@ -2,8 +2,8 @@
Milestone 0 establishes DocForge2 as the successor repository without renaming or replacing the Milestone 0 establishes DocForge2 as the successor repository without renaming or replacing the
working DocForge interfaces. Compatibility changes require an explicit decision, a contract-test working DocForge interfaces. Compatibility changes require an explicit decision, a contract-test
update, and migration guidance. DocForge 1.4.0 preserves that baseline and adds the adapter, update, and migration guidance. DocForge 2.0.0 preserves that baseline and the complete 1.4
rendering, recovery, and release surfaces recorded below. adapter, rendering, recovery, and release surfaces recorded below.
The compatibility gate is: The compatibility gate is:
@ -69,9 +69,9 @@ names and arguments remain supported. Additive commands, tools, and response fie
Removing or changing an existing name, required argument, stable error code, or safety boundary Removing or changing an existing name, required argument, stable error code, or safety boundary
requires an explicit compatibility decision. requires an explicit compatibility decision.
Version `1.4.0` comes from one `docforge._version` authority. The four maintained executable Version `2.0.0` comes from one `docforge._version` authority. The four maintained executable
surfaces report `docforge 1.4.0`, `docforge-mcp 1.4.0`, surfaces report `docforge 2.0.0`, `docforge-mcp 2.0.0`,
`python -m docforge.reference_mcp 1.4.0`, and `docforge-viewer-manager 1.4.0` for `--version`. `python -m docforge.reference_mcp 2.0.0`, and `docforge-viewer-manager 2.0.0` for `--version`.
Generated generic and adapter client configurations include and hash-bind the same Generated generic and adapter client configurations include and hash-bind the same
`docforge_version`. `docforge_version`.
@ -285,7 +285,7 @@ other users and ordinary path access; deliberate arbitrary tampering by another
same operating-system UID is outside the compatibility boundary. same operating-system UID is outside the compatibility boundary.
The historical `v1.0.0` release carried distribution metadata `1.0.0` while its module and MCP The historical `v1.0.0` release carried distribution metadata `1.0.0` while its module and MCP
runtime reported `0.15.0`. Version 1.4.0 records that inherited mismatch in its maintained runtime reported `0.15.0`. Version 2.0.0 records that inherited mismatch in its maintained
migration proof and resolves current identity through one authority. See migration proof and resolves current identity through one authority. See
[migrating from v1](MIGRATING_FROM_V1.md). [migrating from v1](MIGRATING_FROM_V1.md).

View file

@ -1,4 +1,4 @@
# DocForge 1.4 contract # DocForge 2.0 contract
## Authority boundary ## Authority boundary
@ -34,7 +34,7 @@ commit when Git is available; it cannot change repository state.
- Reference adapter configuration: `schemas/reference-adapter.schema.json`, version 1. - Reference adapter configuration: `schemas/reference-adapter.schema.json`, version 1.
- Index schema: version 3, disposable and reproducible. - Index schema: version 3, disposable and reproducible.
- Index attestation: schema version 1, disposable and reproducible. - Index attestation: schema version 1, disposable and reproducible.
- Distribution, Python package, CLI, generic MCP, reference MCP, and viewer manager: version 1.4.0. - Distribution, Python package, CLI, generic MCP, reference MCP, and viewer manager: version 2.0.0.
- Incremental extraction cache: version 1, disposable and reproducible. - Incremental extraction cache: version 1, disposable and reproducible.
Schema files describe the generic interchange contract. Runtime validation remains responsible for Schema files describe the generic interchange contract. Runtime validation remains responsible for
@ -42,8 +42,8 @@ path confinement, source hashing, relationship resolution, dependency cycles, pr
state, and adapter-specific rules that JSON Schema cannot prove by itself. state, and adapter-specific rules that JSON Schema cannot prove by itself.
`src/docforge/_version.py` is the sole package-version authority. The maintained executable `src/docforge/_version.py` is the sole package-version authority. The maintained executable
surfaces report exactly `docforge 1.4.0`, `docforge-mcp 1.4.0`, surfaces report exactly `docforge 2.0.0`, `docforge-mcp 2.0.0`,
`python -m docforge.reference_mcp 1.4.0`, and `docforge-viewer-manager 1.4.0` for `--version`. `python -m docforge.reference_mcp 2.0.0`, and `docforge-viewer-manager 2.0.0` for `--version`.
Generated generic and adapter client configurations bind `docforge_version` into their validated Generated generic and adapter client configurations bind `docforge_version` into their validated
hashes. hashes.

View file

@ -191,7 +191,10 @@ An explicit project integration may construct the full fixed surface only after
confined proposal policy and startup-bound writer. Adapter proposal validators may narrow the confined proposal policy and startup-bound writer. Adapter proposal validators may narrow the
writer's declared operations further. They cannot add arbitrary tools or weaken core changeset writer's declared operations further. They cannot add arbitrary tools or weaken core changeset
validation. The fixed application tool is registered only through the separate canonical applier validation. The fixed application tool is registered only through the separate canonical applier
gate. gate. Application accepts only changesets created by the applier identity unless the project-owned
factory explicitly supplies `accepted_proposal_writers`. Every accepted identity must already be a
configured proposal writer. This allowlist permits review and acceptance across process identities;
it does not grant proposal mutation or application tools to a contributor process.
## Isolated proposal tools ## Isolated proposal tools

View file

@ -22,13 +22,13 @@ Disposable index and cache schemas may change. Rebuild them rather than copying
## Maintained v1.0.0 migration evidence ## Maintained v1.0.0 migration evidence
`make migration-m5` archives and executes the actual annotated `v1.0.0` release, then opens its `make migration-m5` archives and executes the actual annotated `v1.0.0` release, then opens its
fixture, index, and active proposal through DocForge 1.4.0. The frozen tag object is fixture, index, and active proposal through DocForge 2.0.0. The frozen tag object is
`2d7d306a37da89f1c860c7f0be161c45386acf61`; it identifies commit `2d7d306a37da89f1c860c7f0be161c45386acf61`; it identifies commit
`593c173b453236a6872d0a4e88e7a51a67a21cde`. `593c173b453236a6872d0a4e88e7a51a67a21cde`.
The tagged release contains an inherited identity mismatch that the migration proof records The tagged release contains an inherited identity mismatch that the migration proof records
rather than hiding: distribution metadata says `1.0.0`, while `docforge.__version__` and the MCP rather than hiding: distribution metadata says `1.0.0`, while `docforge.__version__` and the MCP
server report `0.15.0`. DocForge 1.4.0 replaces that duplicated state with one authoritative server report `0.15.0`. DocForge 2.0.0 replaces that duplicated state with one authoritative
version and requires its package and server values to agree. version and requires its package and server values to agree.
The maintained fixture evidence is exact: The maintained fixture evidence is exact:

View file

@ -1,133 +0,0 @@
# DocForge2 Milestone 0 closeout
Milestone 0 establishes the public DocForge2 successor without changing the supported `docforge`
product identity or the legacy DocForge repository.
## Repository state
- Public successor: <https://repo.andraxion.net/administrator/DocForge2>
- Successor default branch: `main`
- Successor development branches: `main` and `dev`
- Local `origin`: `forgejo@repo.andraxion.net:administrator/DocForge2.git`
- Local `legacy`: `forgejo@repo.andraxion.net:administrator/DocForge.git`
- Legacy repository: private, nonempty, unarchived, and defaulted to `main`
- Existing annotated tag: `v1.0.0`
`main` is the last completely verified milestone. `dev` is the integration branch for the next
explicitly activated milestone and begins at the same commit. Historical development branch names
remain in the legacy repository as v1 evidence; they are not replicated as active DocForge2
branches.
No tag, release, release announcement, or production integration change was made.
## Preserved lineage
The migration began from the advanced
`codex/language-agnostic-onboarding` tip
`bb13258861175aafd0e6c03c1a5235cbaddf6db2`, nine linear commits beyond the legacy `main`.
The original seven-file no-AST working patch had SHA-256:
```text
8cd10759c232cd4cf8c5eb024e31bbfed355e0be31fc3e732e1567c1a19a887e
```
It was preserved in commit `6c05607` before other integration. The independent local-only adapter
lifecycle commit `1ef76f0271bb339bc0d7eeb62f996d6d680548cb` was retained unchanged and merged by
`15a9130`. The resulting successor `main` contains every commit that was reachable from any local
ref before migration. No rebase, reset, squash, shallow seed, or older-remote seed was used.
A verified pre-migration bundle was written outside the repository:
```text
/tmp/DocForge2-milestone0-candidate-20260729.bundle
SHA-256 6cccd4ae2a65fa2d81e324e4592bee488b111942a496fb85f7ac6b8bd319ea2d
```
## Legacy integrity
Before and after the successor push, the legacy repository advertised these exact heads:
```text
Dev-Rewrite 73165c9f511485ea397aaa00c5e0047bd3e635e2
DocForge-Dev 82b3b905212e7949c0a440879f3bf866197c3927
codex/adapter-authoring-docs 7bc2ac1e3f7f9cf23ec4dcad108f9bb59978ca73
codex/language-agnostic-onboarding bb13258861175aafd0e6c03c1a5235cbaddf6db2
main 9fcafc290c5b5ee9cb83c4c3b2ff600f75210c8e
```
The annotated `v1.0.0` tag object remained
`2d7d306a37da89f1c860c7f0be161c45386acf61`, pointing to
`593c173b453236a6872d0a4e88e7a51a67a21cde`.
No push, deletion, visibility change, archive operation, or default-branch change was performed
against `legacy`.
## Compatibility and correctness
The stable guarantees are recorded in
[`COMPATIBILITY.md`](COMPATIBILITY.md). The dedicated contract gate verifies:
- Distribution, package, imports, and three executable names.
- CLI command and MCP tool names.
- Published JSON schemas and representative runtime envelopes.
- One-method `load_projection()` adapters.
- Optional incremental behavior and full-projection equivalence.
- Canonical JSON changeset hashing.
- Complete no-AST behavior, including pre-existing Logic, viewer, and application-refresh paths.
The no-AST policy does not claim to inspect arbitrary adapter internals. It enforces the owner-bound
policy at Logic publication and retrieval surfaces while preserving complete-projection and
genuinely non-AST incremental adapters.
## Security and publication checks
Gitleaks 8.30.1 scanned reachable Git history and an exact archive of the candidate tree with full
redaction. Both scans reported zero findings. `git fsck --full` passed. A broader filename and
credential-pattern audit also found no high-confidence matches.
Forgejo repository creation used one timestamped short-lived administrator token. Forgejo accepted
it for repository creation but returned HTTP 401 when it attempted self-deletion. The exact
task-created token row was then validated by ID, owner, and unique name, deleted in one SQLite
transaction, and rechecked. Zero matching temporary token rows remain.
The package metadata declares MIT, but the repository has no tracked standalone `LICENSE`,
`COPYING`, or `NOTICE` file. Milestone 0 records that publication weakness without inventing or
changing legal terms.
## Validation and fresh-clone proof
The repository-native aggregate gate is:
```bash
make gate
```
It passed in the working tree and in an anonymous HTTPS clone of the public successor. The
fresh-clone proof:
- Selected the expected `main` commit through the public default branch.
- Passed `git fsck --full`.
- Recreated the Python virtual environment from `uv.lock`.
- Recreated JavaScript dependencies with `npm ci`, with zero reported vulnerabilities.
- Passed Ruff formatting and lint.
- Passed HTML, rendered-manual HTML, CSS, and JavaScript lint.
- Passed Pyright with zero diagnostics.
- Passed Python compilation.
- Passed 8 public-contract tests and 42 schema subtests.
- Passed the complete 95-test and 44-subtest warning-strict suite.
- Passed lock and dependency-tree checks.
- Built the wheel and source distribution.
- Passed the disposable benchmark smoke run.
- Remained clean after validation.
The maintained performance evidence and known gaps are recorded in
[`MILESTONE_0_BASELINE.md`](MILESTONE_0_BASELINE.md) and
[`benchmarks/milestone0-2026-07-29.json`](../benchmarks/milestone0-2026-07-29.json).
## Scope confirmation
Milestone 0 made no speculative storage rewrite and introduced no self-hosting dependency.
WorldForge and ScrapeStation were not read as benchmark fixtures or changed. No production MCP
integration was repointed. `ManualRenderPlan`, `GraphViewPlan`, and a portable graph renderer remain
later-milestone direction, not claimed implementation.

View file

@ -43,9 +43,10 @@ Capability modes are:
- `operator`: reserved; it currently adds no tools. - `operator`: reserved; it currently adds no tools.
Mode describes the maximum registered surface. Actual authority can be narrower. A descriptor must Mode describes the maximum registered surface. Actual authority can be narrower. A descriptor must
declare the selected writer, including allowed families and operation types. Application requires declare the selected writer, including allowed families and operation types. Application defaults
the matching configured writer, changeset creator, and canonical-applier identity. A mode name to a matching configured writer, changeset creator, and canonical-applier identity. A project-owned
cannot create a missing descriptor grant. server may explicitly authorize its applier to accept changesets from additional configured writers.
A mode name cannot create a missing descriptor grant or extend that accepted-writer allowlist.
Generic generated client fragments default to read mode. Other construction paths preserve their Generic generated client fragments default to read mode. Other construction paths preserve their
documented compatible factory defaults. Treat `docforge_bootstrap.session_contract` and its actual documented compatible factory defaults. Treat `docforge_bootstrap.session_contract` and its actual

View file

@ -5,8 +5,8 @@ people and AI agents can search, inspect, visualize, and change through reviewab
Canonical project files remain authoritative. The SQLite graph, previews, rendered manuals, and Canonical project files remain authoritative. The SQLite graph, previews, rendered manuals, and
viewer processes are derived and can be rebuilt. viewer processes are derived and can be rebuilt.
This manual describes the DocForge 1.4.0 release. The tagged `v1.0.0` baseline was the first stable This manual describes the DocForge 2.0.0 release. The tagged `v1.0.0` baseline was the first stable
product release. Version 1.4.0 preserves its project-scoped graph, CLI and MCP query product release. Version 2.0.0 preserves its project-scoped graph, CLI and MCP query
surfaces, hash-approved proposal application, generic and project-owned adapters, declared surfaces, hash-approved proposal application, generic and project-owned adapters, declared
rendering, and Nodes/Flow/Web model while adding the maintained incremental, projection, adapter rendering, and Nodes/Flow/Web model while adding the maintained incremental, projection, adapter
SDK, recovery, and release proofs documented below. SDK, recovery, and release proofs documented below.
@ -132,8 +132,8 @@ python -m docforge.reference_mcp --version
python -m docforge.viewer_manager --version python -m docforge.viewer_manager --version
``` ```
For version 1.4.0 these report `docforge 1.4.0`, `docforge-mcp 1.4.0`, For version 2.0.0 these report `docforge 2.0.0`, `docforge-mcp 2.0.0`,
`python -m docforge.reference_mcp 1.4.0`, and `docforge-viewer-manager 1.4.0`. Package metadata, `python -m docforge.reference_mcp 2.0.0`, and `docforge-viewer-manager 2.0.0`. Package metadata,
Python imports, generated generic and adapter configurations, and these commands share the same Python imports, generated generic and adapter configurations, and these commands share the same
version authority. version authority.
@ -815,7 +815,14 @@ docforge-mcp \
``` ```
Without `--canonical-applier`, `docforge_apply_changeset` is not registered. The flag is an Without `--canonical-applier`, `docforge_apply_changeset` is not registered. The flag is an
identity, not a command. The changeset creator, configured writer, and canonical applier must agree. identity, not a command. Generic CLI and MCP application require the changeset creator, configured
writer, and canonical applier to agree.
A project-owned adapter server can separately pass `accepted_proposal_writers` to
`create_project_server`. This explicit allowlist lets its startup-bound applier accept an exact
reviewed changeset from another configured contributor identity. The default remains the applier
identity only. Accepted contributors retain their original proposal permissions and do not receive
canonical application authority.
Call `docforge_bootstrap` first. Its version-1 `session_contract` contains the fixed binding, Call `docforge_bootstrap` first. Its version-1 `session_contract` contains the fixed binding,
current graph generation, effective policy, actual capabilities, render policies, prohibitions, current graph generation, effective policy, actual capabilities, render policies, prohibitions,
@ -1303,7 +1310,7 @@ make fresh-clone-m5
recovery, task-evidence, fresh-wheel, version, artifact, secret-scan, and benchmark suite. recovery, task-evidence, fresh-wheel, version, artifact, secret-scan, and benchmark suite.
`fresh-clone-m5` anonymously clones the exact published candidate over HTTPS, fetches and verifies `fresh-clone-m5` anonymously clones the exact published candidate over HTTPS, fetches and verifies
the frozen annotated `v1.0.0` migration tag, and repeats `release-gate`. Release operators use the frozen annotated `v1.0.0` migration tag, and repeats `release-gate`. Release operators use
`make release-pretag` before creating `v1.4.0` and `make release-posttag` after the annotated tag `make release-pretag` before creating `v2.0.0` and `make release-posttag` after the annotated tag
points to the exact release commit. points to the exact release commit.
Project-specific vocabulary, extraction rules, and serialization belong in the project adapter. Project-specific vocabulary, extraction rules, and serialization belong in the project adapter.

View file

@ -1,3 +1,3 @@
"""Single authoritative DocForge distribution and runtime version.""" """Single authoritative DocForge distribution and runtime version."""
__version__ = "1.4.0" __version__ = "2.0.0"

View file

@ -1204,12 +1204,39 @@ class CanonicalApplicationService:
*, *,
applier_id: str | None, applier_id: str | None,
applier: CanonicalApplier | None, applier: CanonicalApplier | None,
accepted_proposal_writers: tuple[str, ...] = (),
index: ProjectIndex | None = None, index: ProjectIndex | None = None,
manual_policy: ManualProjectionMode = "auto", manual_policy: ManualProjectionMode = "auto",
) -> None: ) -> None:
self.project = project self.project = project
self.applier_id = applier_id self.applier_id = applier_id
self.applier = applier self.applier = applier
if accepted_proposal_writers and (applier_id is None or applier is None):
raise DocForgeError(
"invalid_application_policy",
"Accepted proposal writers require an enabled canonical applier",
)
configured_writers = frozenset(
writer.writer_id for writer in project.descriptor.proposal_writers
)
selected_writers = (
accepted_proposal_writers
if accepted_proposal_writers
else ((applier_id,) if applier_id is not None else ())
)
if len(set(selected_writers)) != len(selected_writers):
raise DocForgeError(
"invalid_application_policy",
"Accepted proposal writer identities must be unique",
)
unknown_writers = sorted(set(selected_writers) - configured_writers)
if unknown_writers:
raise DocForgeError(
"invalid_application_policy",
"Accepted proposal writers must be configured for this project",
writers=unknown_writers,
)
self.accepted_proposal_writers = tuple(sorted(selected_writers))
self.changesets = ChangesetStore(project, applier_id) self.changesets = ChangesetStore(project, applier_id)
self.index = index or ProjectIndex(project) self.index = index or ProjectIndex(project)
self.manual_policy = validate_manual_projection_mode(manual_policy) self.manual_policy = validate_manual_projection_mode(manual_policy)
@ -1227,6 +1254,9 @@ class CanonicalApplicationService:
return { return {
"enabled": self.enabled, "enabled": self.enabled,
"applier": self.applier_id if self.enabled else None, "applier": self.applier_id if self.enabled else None,
"accepted_proposal_writers": (
list(self.accepted_proposal_writers) if self.enabled else []
),
} }
def apply(self, changeset_id: str, expected_changeset_hash: str) -> dict[str, object]: def apply(self, changeset_id: str, expected_changeset_hash: str) -> dict[str, object]:
@ -1239,6 +1269,7 @@ class CanonicalApplicationService:
changeset_id=changeset_id, changeset_id=changeset_id,
expected_changeset_hash=expected_changeset_hash, expected_changeset_hash=expected_changeset_hash,
applier_id=self.applier_id, applier_id=self.applier_id,
accepted_creator_ids=frozenset(self.accepted_proposal_writers),
application=self.applier.apply, application=self.applier.apply,
) )
refresh_errors: list[dict[str, object]] = [] refresh_errors: list[dict[str, object]] = []

View file

@ -598,6 +598,7 @@ class ChangesetStore:
changeset_id: str, changeset_id: str,
expected_changeset_hash: str, expected_changeset_hash: str,
applier_id: str, applier_id: str,
accepted_creator_ids: frozenset[str] | None = None,
application: Callable[ application: Callable[
[ProjectSnapshot, ProjectSnapshot, tuple[Mapping[str, object], ...]], [ProjectSnapshot, ProjectSnapshot, tuple[Mapping[str, object], ...]],
dict[str, object], dict[str, object],
@ -628,13 +629,17 @@ class ChangesetStore:
expected=expected_changeset_hash, expected=expected_changeset_hash,
actual=actual_hash, actual=actual_hash,
) )
if document["creator"] != applier_id: accepted_creators = (
frozenset({applier_id}) if accepted_creator_ids is None else accepted_creator_ids
)
if document["creator"] not in accepted_creators:
raise DocForgeError( raise DocForgeError(
"changeset_owner_conflict", "changeset_owner_conflict",
"Canonical applier does not own this changeset", "Canonical applier is not authorized to accept this changeset creator",
changeset_id=changeset_id, changeset_id=changeset_id,
owner=document["creator"], owner=document["creator"],
applier=applier_id, applier=applier_id,
accepted_creators=sorted(accepted_creators),
) )
projected = ProjectSnapshot( projected = ProjectSnapshot(
descriptor=snapshot.descriptor, descriptor=snapshot.descriptor,
@ -680,6 +685,8 @@ class ChangesetStore:
lifecycle=lifecycle, lifecycle=lifecycle,
applied_from_revision=snapshot.revision, applied_from_revision=snapshot.revision,
applied_from_source_hash=snapshot.source_hash, applied_from_source_hash=snapshot.source_hash,
proposal_creator=document["creator"],
applied_by=applier_id,
**payload, **payload,
) )

View file

@ -133,6 +133,7 @@ class DocForgeService:
*, *,
canonical_applier_id: str | None = None, canonical_applier_id: str | None = None,
canonical_applier: CanonicalApplier | None = None, canonical_applier: CanonicalApplier | None = None,
accepted_proposal_writers: tuple[str, ...] = (),
context_provider: ContextProvider = compile_context, context_provider: ContextProvider = compile_context,
tool_surface: tuple[str, ...] | None = None, tool_surface: tuple[str, ...] | None = None,
binding_metadata: Mapping[str, object] | None = None, binding_metadata: Mapping[str, object] | None = None,
@ -188,6 +189,7 @@ class DocForgeService:
self.project, self.project,
applier_id=canonical_applier_id if application_enabled else None, applier_id=canonical_applier_id if application_enabled else None,
applier=canonical_applier if application_enabled else None, applier=canonical_applier if application_enabled else None,
accepted_proposal_writers=(accepted_proposal_writers if application_enabled else ()),
index=self.index, index=self.index,
manual_policy=self.projection_policy.manual, manual_policy=self.projection_policy.manual,
) )
@ -2108,6 +2110,7 @@ def create_project_server(
proposal_writer: str | None = None, proposal_writer: str | None = None,
canonical_applier_id: str | None = None, canonical_applier_id: str | None = None,
canonical_applier: CanonicalApplier | None = None, canonical_applier: CanonicalApplier | None = None,
accepted_proposal_writers: tuple[str, ...] = (),
context_provider: ContextProvider = compile_context, context_provider: ContextProvider = compile_context,
binding_metadata: Mapping[str, object] | None = None, binding_metadata: Mapping[str, object] | None = None,
no_ast: bool = False, no_ast: bool = False,
@ -2124,6 +2127,7 @@ def create_project_server(
proposal_writer, proposal_writer,
canonical_applier_id=canonical_applier_id, canonical_applier_id=canonical_applier_id,
canonical_applier=canonical_applier, canonical_applier=canonical_applier,
accepted_proposal_writers=accepted_proposal_writers,
context_provider=context_provider, context_provider=context_provider,
binding_metadata=binding_metadata, binding_metadata=binding_metadata,
no_ast=no_ast, no_ast=no_ast,

View file

@ -397,7 +397,9 @@ def _load_descriptor(root: Path) -> ProjectDescriptor:
title = require_string(document, "title", descriptor_path) title = require_string(document, "title", descriptor_path)
adapter = require_string(document, "adapter", descriptor_path) adapter = require_string(document, "adapter", descriptor_path)
if adapter != "generic": if adapter != "generic":
raise DocForgeError("unsupported_adapter", "DFG-1 supports only the generic adapter") raise DocForgeError(
"unsupported_adapter", "This project loader supports only the generic adapter"
)
sources = document.get("sources") sources = document.get("sources")
derived = document.get("derived") derived = document.get("derived")

View file

@ -328,6 +328,103 @@ class DocForgeChangesetTests(unittest.TestCase):
self.assertFalse((root / ".docforge/changesets/.state/update-race.json").exists()) self.assertFalse((root / ".docforge/changesets/.state/update-race.json").exists())
self.assertFalse(tuple(target.parent.glob(".docforge-apply-*"))) self.assertFalse(tuple(target.parent.glob(".docforge-apply-*")))
def test_explicit_applier_accepts_a_configured_contributor_changeset(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture(Path(directory))
descriptor = root / ".docforge/project.toml"
descriptor.write_text(
descriptor.read_text(encoding="utf-8")
+ """
[[changesets.writers]]
id = "contributor"
families = ["guide"]
operations = ["update"]
""",
encoding="utf-8",
)
project = Project.open(root)
proposal = ChangesetStore(project, "contributor").register(
"contributor-update",
[
{
"operation": "update",
"node_id": "guide.workflow",
"metadata": {"summary": "Accepted from a configured contributor."},
"rationale": "Prove explicit cross-identity acceptance.",
}
],
)
default_service = CanonicalApplicationService(
project,
applier_id="alpha-editor",
applier=GenericCanonicalApplier(project),
)
with self.assertRaises(DocForgeError) as denied:
default_service.apply(
"contributor-update",
str(proposal["changeset_hash"]),
)
self.assertEqual("changeset_owner_conflict", denied.exception.code)
service = CanonicalApplicationService(
project,
applier_id="alpha-editor",
applier=GenericCanonicalApplier(project),
accepted_proposal_writers=("alpha-editor", "contributor"),
)
result = service.apply(
"contributor-update",
str(proposal["changeset_hash"]),
)
self.assertTrue(result["applied"])
self.assertEqual("contributor", result["proposal_creator"])
self.assertEqual("alpha-editor", result["applied_by"])
self.assertEqual(
["alpha-editor", "contributor"],
service.access()["accepted_proposal_writers"],
)
workflow = next(
node for node in project.load().nodes if node.node_id == "guide.workflow"
)
self.assertEqual(
"Accepted from a configured contributor.",
workflow.summary,
)
def test_application_rejects_unknown_or_duplicate_accepted_writers(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture(Path(directory))
project = Project.open(root)
with self.assertRaises(DocForgeError) as unknown:
CanonicalApplicationService(
project,
applier_id="alpha-editor",
applier=GenericCanonicalApplier(project),
accepted_proposal_writers=("missing-writer",),
)
with self.assertRaises(DocForgeError) as duplicate:
CanonicalApplicationService(
project,
applier_id="alpha-editor",
applier=GenericCanonicalApplier(project),
accepted_proposal_writers=("alpha-editor", "alpha-editor"),
)
with self.assertRaises(DocForgeError) as disabled:
CanonicalApplicationService(
project,
applier_id=None,
applier=None,
accepted_proposal_writers=("alpha-editor",),
)
self.assertEqual("invalid_application_policy", unknown.exception.code)
self.assertEqual("invalid_application_policy", duplicate.exception.code)
self.assertEqual("invalid_application_policy", disabled.exception.code)
def test_canonical_create_and_delete_races_preserve_foreign_targets(self) -> None: def test_canonical_create_and_delete_races_preserve_foreign_targets(self) -> None:
with tempfile.TemporaryDirectory() as directory: with tempfile.TemporaryDirectory() as directory:
parent = Path(directory) parent = Path(directory)

View file

@ -17,6 +17,7 @@ from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client from mcp.client.stdio import stdio_client
from mcp.shared.memory import create_connected_server_and_client_session from mcp.shared.memory import create_connected_server_and_client_session
from docforge.application import GenericCanonicalApplier
from docforge.changesets import ChangesetStore from docforge.changesets import ChangesetStore
from docforge.errors import DocForgeError from docforge.errors import DocForgeError
from docforge.index import ProjectIndex from docforge.index import ProjectIndex
@ -29,6 +30,7 @@ from docforge.mcp_server import (
SERVER_VERSION, SERVER_VERSION,
DocForgeService, DocForgeService,
_create_bound_server, _create_bound_server,
create_project_server,
create_server, create_server,
) )
from docforge.project import Project, project_root_fingerprint from docforge.project import Project, project_root_fingerprint
@ -1446,6 +1448,83 @@ class DocForgeMcpTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual("Applied through the gated MCP tool.", workflow.summary) self.assertEqual("Applied through the gated MCP tool.", workflow.summary)
self.assertTrue((root / ".docforge/rendered/manual.html").is_file()) self.assertTrue((root / ".docforge/rendered/manual.html").is_file())
async def test_project_server_accepts_an_explicit_contributor_without_granting_apply(
self,
) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture("alpha", Path(directory))
descriptor = root / ".docforge/project.toml"
descriptor.write_text(
descriptor.read_text(encoding="utf-8")
+ """
[[changesets.writers]]
id = "contributor"
families = ["guide"]
operations = ["update"]
""",
encoding="utf-8",
)
project = Project.open(root)
ProjectIndex(project).build()
async with create_connected_server_and_client_session(
create_server(
root,
"contributor",
capability_mode="proposal",
),
raise_exceptions=True,
) as contributor:
contributor_tools = tuple(
tool.name for tool in (await contributor.list_tools()).tools
)
registered = await contributor.call_tool(
"docforge_register_changes",
{
"changeset_id": "accepted-contribution",
"operations": [
{
"operation": "update",
"node_id": "guide.workflow",
"metadata": {"summary": "Accepted through a separate applier."},
"rationale": "Prove explicit contributor acceptance over MCP.",
}
],
},
)
async with create_connected_server_and_client_session(
create_project_server(
project,
proposal_writer="alpha-editor",
canonical_applier_id="alpha-editor",
canonical_applier=GenericCanonicalApplier(project),
accepted_proposal_writers=("contributor",),
capability_mode="application",
),
raise_exceptions=True,
) as developer:
contract = await developer.call_tool("docforge_get_contract", {})
applied = await developer.call_tool(
"docforge_apply_changeset",
{
"changeset_id": "accepted-contribution",
"expected_changeset_hash": registered.structuredContent["changeset_hash"],
},
)
self.assertEqual(ALL_TOOLS, contributor_tools)
self.assertNotIn("docforge_apply_changeset", contributor_tools)
self.assertEqual(
["contributor"],
contract.structuredContent["canonical_application_access"][
"accepted_proposal_writers"
],
)
self.assertTrue(applied.structuredContent["applied"])
self.assertEqual("contributor", applied.structuredContent["proposal_creator"])
self.assertEqual("alpha-editor", applied.structuredContent["applied_by"])
async def test_stdio_transport_serves_the_same_project_bound_contract(self) -> None: async def test_stdio_transport_serves_the_same_project_bound_contract(self) -> None:
with tempfile.TemporaryDirectory() as directory: with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture("beta", Path(directory)) root = self.copy_fixture("beta", Path(directory))

View file

@ -10,7 +10,7 @@ class Milestone5MigrationTests(unittest.TestCase):
evidence = build_migration_evidence() evidence = build_migration_evidence()
self.assertEqual("v1.0.0", evidence["tag"]) self.assertEqual("v1.0.0", evidence["tag"])
self.assertEqual("1.4.0", evidence["current"]["version"]) self.assertEqual("2.0.0", evidence["current"]["version"])
self.assertEqual(1, evidence["current"]["index_schema_before"]) self.assertEqual(1, evidence["current"]["index_schema_before"])
self.assertEqual(3, evidence["current"]["index_schema_after"]) self.assertEqual(3, evidence["current"]["index_schema_after"])
self.assertEqual( self.assertEqual(

View file

@ -282,10 +282,10 @@ class PublicContractTests(unittest.TestCase):
self.assertTrue(hasattr(module, name)) self.assertTrue(hasattr(module, name))
version_surfaces = { version_surfaces = {
"docforge.cli": "docforge 1.4.0\n", "docforge.cli": "docforge 2.0.0\n",
"docforge.mcp_server": "docforge-mcp 1.4.0\n", "docforge.mcp_server": "docforge-mcp 2.0.0\n",
"docforge.reference_mcp": "python -m docforge.reference_mcp 1.4.0\n", "docforge.reference_mcp": "python -m docforge.reference_mcp 2.0.0\n",
"docforge.viewer_manager": "docforge-viewer-manager 1.4.0\n", "docforge.viewer_manager": "docforge-viewer-manager 2.0.0\n",
} }
for module_name, expected in version_surfaces.items(): for module_name, expected in version_surfaces.items():
with self.subTest(module=module_name): with self.subTest(module=module_name):

View file

@ -14,13 +14,13 @@ class ReleaseIdentityTests(unittest.TestCase):
) )
self.assertEqual(1, evidence["schema_version"]) self.assertEqual(1, evidence["schema_version"])
self.assertEqual("1.4.0", evidence["version"]) self.assertEqual("2.0.0", evidence["version"])
self.assertEqual( self.assertEqual(
{ {
"docforge.cli": "docforge 1.4.0", "docforge.cli": "docforge 2.0.0",
"docforge.mcp_server": "docforge-mcp 1.4.0", "docforge.mcp_server": "docforge-mcp 2.0.0",
"docforge.reference_mcp": "python -m docforge.reference_mcp 1.4.0", "docforge.reference_mcp": "python -m docforge.reference_mcp 2.0.0",
"docforge.viewer_manager": "docforge-viewer-manager 1.4.0", "docforge.viewer_manager": "docforge-viewer-manager 2.0.0",
}, },
evidence["surfaces"], evidence["surfaces"],
) )

View file

@ -39,7 +39,7 @@ REQUIRED_MILESTONE_4_PAGES = (
Path("README.md"), Path("README.md"),
Path("docs/ADAPTER_AUTHORING_GUIDE.md"), Path("docs/ADAPTER_AUTHORING_GUIDE.md"),
Path("docs/AGENT_INTEGRATION.md"), Path("docs/AGENT_INTEGRATION.md"),
Path("docs/APPLICATION_DECISION.md"), Path("docs/CANONICAL_APPLICATION.md"),
Path("docs/COMMAND_REFERENCE.md"), Path("docs/COMMAND_REFERENCE.md"),
Path("docs/COMPATIBILITY.md"), Path("docs/COMPATIBILITY.md"),
Path("docs/CONTRACT.md"), Path("docs/CONTRACT.md"),