repopact 2.0.1__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. {repopact-2.0.1 → repopact-2.1.0}/PKG-INFO +26 -9
  2. {repopact-2.0.1 → repopact-2.1.0}/README.md +161 -144
  3. repopact-2.1.0/VERSION +1 -0
  4. {repopact-2.0.1 → repopact-2.1.0}/pyproject.toml +7 -6
  5. repopact-2.1.0/schemas/conformance-manifest.schema.json +34 -0
  6. {repopact-2.0.1 → repopact-2.1.0}/schemas/work-item.schema.json +1 -2
  7. {repopact-2.0.1 → repopact-2.1.0}/scripts/init_repo.py +12 -8
  8. {repopact-2.0.1 → repopact-2.1.0}/scripts/new.py +10 -4
  9. {repopact-2.0.1 → repopact-2.1.0}/scripts/plan_import.py +11 -4
  10. {repopact-2.0.1 → repopact-2.1.0}/scripts/repo_model.py +1 -2
  11. {repopact-2.0.1 → repopact-2.1.0}/scripts/repopact.egg-info/PKG-INFO +26 -9
  12. {repopact-2.0.1 → repopact-2.1.0}/scripts/repopact.egg-info/SOURCES.txt +2 -0
  13. {repopact-2.0.1 → repopact-2.1.0}/scripts/repopact.egg-info/top_level.txt +1 -0
  14. {repopact-2.0.1 → repopact-2.1.0}/scripts/repopact_cli.py +14 -10
  15. repopact-2.1.0/scripts/run_conformance.py +112 -0
  16. {repopact-2.0.1 → repopact-2.1.0}/scripts/takeover.py +1 -1
  17. {repopact-2.0.1 → repopact-2.1.0}/scripts/validate_repo.py +12 -5
  18. {repopact-2.0.1 → repopact-2.1.0}/tests/test_conformance.py +27 -6
  19. {repopact-2.0.1 → repopact-2.1.0}/tests/test_validate_repo.py +64 -28
  20. repopact-2.0.1/VERSION +0 -1
  21. {repopact-2.0.1 → repopact-2.1.0}/LICENSE +0 -0
  22. {repopact-2.0.1 → repopact-2.1.0}/schemas/audit-finding.schema.json +0 -0
  23. {repopact-2.0.1 → repopact-2.1.0}/schemas/evidence-run.schema.json +0 -0
  24. {repopact-2.0.1 → repopact-2.1.0}/schemas/frozen-surface.schema.json +0 -0
  25. {repopact-2.0.1 → repopact-2.1.0}/schemas/invariants.schema.json +0 -0
  26. {repopact-2.0.1 → repopact-2.1.0}/schemas/record-frontmatter.schema.json +0 -0
  27. {repopact-2.0.1 → repopact-2.1.0}/scripts/adopt_repo.py +0 -0
  28. {repopact-2.0.1 → repopact-2.1.0}/scripts/check_frozen_surface.py +0 -0
  29. {repopact-2.0.1 → repopact-2.1.0}/scripts/doctor.py +0 -0
  30. {repopact-2.0.1 → repopact-2.1.0}/scripts/frontmatter.py +0 -0
  31. {repopact-2.0.1 → repopact-2.1.0}/scripts/generate_dashboard.py +0 -0
  32. {repopact-2.0.1 → repopact-2.1.0}/scripts/generate_spec.py +0 -0
  33. {repopact-2.0.1 → repopact-2.1.0}/scripts/repopact.egg-info/dependency_links.txt +0 -0
  34. {repopact-2.0.1 → repopact-2.1.0}/scripts/repopact.egg-info/entry_points.txt +0 -0
  35. {repopact-2.0.1 → repopact-2.1.0}/scripts/repopact.egg-info/requires.txt +0 -0
  36. {repopact-2.0.1 → repopact-2.1.0}/scripts/track_import.py +0 -0
  37. {repopact-2.0.1 → repopact-2.1.0}/setup.cfg +0 -0
  38. {repopact-2.0.1 → repopact-2.1.0}/templates/README.md +0 -0
  39. {repopact-2.0.1 → repopact-2.1.0}/templates/decision.md +0 -0
  40. {repopact-2.0.1 → repopact-2.1.0}/templates/evidence-run.json +0 -0
  41. {repopact-2.0.1 → repopact-2.1.0}/templates/policy.md +0 -0
  42. {repopact-2.0.1 → repopact-2.1.0}/templates/work-item.README.md +0 -0
  43. {repopact-2.0.1 → repopact-2.1.0}/templates/work-item.json +0 -0
  44. {repopact-2.0.1 → repopact-2.1.0}/tests/test_plan_import_links.py +0 -0
  45. {repopact-2.0.1 → repopact-2.1.0}/tests/test_takeover_refs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: repopact
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: A repository-native standard for durable agent work: binding invariants, evidence-gated work items, and drift audits.
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/ForgeWireLabs/repopact
@@ -22,15 +22,15 @@ cannot be silently weakened.
22
22
  > The repository is the pact: authority, intent, evidence, and history that survive every
23
23
  > session.
24
24
 
25
- `pip install repopact` · Apache-2.0 · current release **2.0.1**
26
- ([changelog](decisions/0021-preflight-mandatory-and-provenance.md)).
25
+ `pip install repopact` · Apache-2.0 · current release **2.1.0**
26
+ ([changelog](decisions/0024-release-2.1.0-proposed-lifecycle.md)).
27
27
 
28
28
  ## How it relates to `AGENTS.md`
29
29
 
30
30
  `AGENTS.md` (and `CLAUDE.md`, editor rules) tell an agent *what to do* — they're
31
31
  instructions, plain Markdown, with no enforcement. RepoPact is the layer **above** them:
32
32
 
33
- > `AGENTS.md` tells the agent what to do. RepoPact proves it didn't quietly undo it.
33
+ > `AGENTS.md` tells an agent how to behave. RepoPact enforces and records whether the work respected the contract.
34
34
 
35
35
  RepoPact's distinguishing primitive is the **binding invariant** — a declared guarantee with
36
36
  a rationale, an escalation path, and (where its logical type permits) a machine enforcer.
@@ -71,7 +71,8 @@ intent -> scoped authority -> work item -> implementation -> evidence -> audit -
71
71
  pip install repopact # the CLI + reference validator, from PyPI
72
72
  repopact init --target ../your-repo # seed a valid RepoPact in a new repo
73
73
  cd ../your-repo
74
- repopact new work-item "Title of the work" # stamps records (incl. the preflight marker)
74
+ repopact new work-item "Title of the work" # stamps active work (incl. the preflight marker)
75
+ repopact new work-item "Candidate idea" --status proposed
75
76
  repopact validate
76
77
  repopact dashboard
77
78
  ```
@@ -83,6 +84,14 @@ repopact dashboard
83
84
  [`AGENTS.md`](AGENTS.md), then [`governance/charter.md`](governance/charter.md) and
84
85
  [`governance/workflow.md`](governance/workflow.md).
85
86
 
87
+ Alternative implementations can run the published conformance suite:
88
+
89
+ ```powershell
90
+ python scripts/run_conformance.py --command "your-validator --root {repo}"
91
+ ```
92
+
93
+ See [`CONFORMANCE.md`](CONFORMANCE.md) and [`conformance/`](conformance/).
94
+
86
95
  ## Adopt an *existing* repository
87
96
 
88
97
  For a project that already has CODEOWNERS, CI workflows, and nested `AGENTS.md` contracts,
@@ -115,9 +124,17 @@ Decision [`0021`](decisions/0021-preflight-mandatory-and-provenance.md) (superse
115
124
 
116
125
  ## Status is a filesystem transition
117
126
 
118
- Work moves between `work/active`, `work/blocked`, `work/deferred`, and `work/completed`; its
119
- `work-item.json` status must match its directory. Moving a work item never deletes its
120
- reasoning, decisions, or evidence links.
127
+ Work moves between lifecycle directories; its `work-item.json` status must match its
128
+ directory. Moving a work item never deletes its reasoning, decisions, or evidence links.
129
+
130
+ - `proposed`: captured candidate work that is not yet accepted or authorized for
131
+ implementation.
132
+ - `active`: accepted work authorized for design or implementation.
133
+ - `blocked`: accepted/current work that cannot proceed until a named condition changes.
134
+ - `deferred`: accepted work intentionally postponed with rationale.
135
+ - `completed`: delivered work whose acceptance criteria are evidence-closed.
136
+
137
+ Active and completed work cannot depend on proposed work as if it were accepted.
121
138
 
122
139
  ## Derive over declare
123
140
 
@@ -132,7 +149,7 @@ holds a [formal model](research/formal-model.md) (the L0–L5 kernel, the typed
132
149
  lattice, the adoption trilemma), the pre-registered [experiment protocol](research/protocol.md)
133
150
  and [benchmark protocol](research/benchmark-protocol.md) (hypotheses H1–H13, falsification
134
151
  criteria, [threats to validity](research/threats-to-validity.md)), a [findings
135
- register](research/findings.md), and a full [paper draft](research/paper.md).
152
+ register](research/findings.md), and the current [paper](research/paper.md).
136
153
 
137
154
  **PactBench** — the runnable benchmark suite (pre-registered tasks measuring whether RepoPact
138
155
  enforcement reduces silent guarantee drift, with a model-agnostic harness and an S5 drift
@@ -1,144 +1,161 @@
1
- # RepoPact
2
-
3
- RepoPact is a **repository-native governance kernel for durable agent work**. It keeps the
4
- load-bearing state of a project — intent, authority, evidence, decisions, and drift — as
5
- typed, version-controlled records in the filesystem, so a new contributor or agent can
6
- recover where things stand *without* a prior conversation, and so the guarantees that matter
7
- cannot be silently weakened.
8
-
9
- > The repository is the pact: authority, intent, evidence, and history that survive every
10
- > session.
11
-
12
- `pip install repopact` · Apache-2.0 · current release **2.0.1**
13
- ([changelog](decisions/0021-preflight-mandatory-and-provenance.md)).
14
-
15
- ## How it relates to `AGENTS.md`
16
-
17
- `AGENTS.md` (and `CLAUDE.md`, editor rules) tell an agent *what to do* — they're
18
- instructions, plain Markdown, with no enforcement. RepoPact is the layer **above** them:
19
-
20
- > `AGENTS.md` tells the agent what to do. RepoPact proves it didn't quietly undo it.
21
-
22
- RepoPact's distinguishing primitive is the **binding invariant** — a declared guarantee with
23
- a rationale, an escalation path, and (where its logical type permits) a machine enforcer.
24
- That, plus evidence-gated completion and a filesystem state machine, is what turns a folder
25
- convention into a contract. `repopact adopt` ingests an existing `AGENTS.md` rather than
26
- replacing it (decision [`0020`](decisions/0020-launch-positioning-layer-above-agents-md.md)).
27
-
28
- ## Core loop
29
-
30
- ```text
31
- intent -> scoped authority -> work item -> implementation -> evidence -> audit -> history
32
- ```
33
-
34
- ![RepoPact core loop over a filesystem state machine](docs/assets/repopact-flow.svg)
35
-
36
- ## Primitives
37
-
38
- 1. **Charter & invariants** — principles (judgment) and binding invariants
39
- (escalation-gated) in `governance/`.
40
- 2. **Frozen surface** — paths and symbols that require operator approval (`--ack`) to change.
41
- 3. **Scopes & roles** — layered `AGENTS.md` contracts plus a role/scope map in
42
- `governance/owners.json`.
43
- 4. **Work items** — narrative `README.md` + machine-readable `work-item.json` with
44
- evidence-linked acceptance criteria. **Mandatory preflight** (2.0): a work item must be
45
- recorded *before* implementation begins (`repopact new` stamps the marker).
46
- 5. **Evidence** — immutable run manifests under `evidence/runs/`.
47
- 6. **Decisions & policies** — durable choices (`decisions/`) and operating rules
48
- (`governance/policies/`) whose rationale outlives any single work item.
49
- 7. **Provenance** (2.0) — every record is `concrete`, `provisional`, or `inferred`.
50
- `adopt` emits provisional/inferred records (honest, not fabricated); `doctor` ratchets
51
- them to `concrete` as real evidence arrives. See *2.0 changes* below.
52
- 8. **Reconciliation** — audits and a *generated* dashboard surface drift and review
53
- staleness rather than hand-maintaining it.
54
-
55
- ## Install & quick start
56
-
57
- ```powershell
58
- pip install repopact # the CLI + reference validator, from PyPI
59
- repopact init --target ../your-repo # seed a valid RepoPact in a new repo
60
- cd ../your-repo
61
- repopact new work-item "Title of the work" # stamps records (incl. the preflight marker)
62
- repopact validate
63
- repopact dashboard
64
- ```
65
-
66
- `repopact` dispatches `init`, `adopt`, `validate`, `new`, `dashboard`, `spec`,
67
- `check-frozen`, `import-plan`, and `doctor`. Records are validated against `schemas/*.json`
68
- (structure) and by the validator (cross-record semantics; decision
69
- [`0003`](decisions/0003-validate-records-against-json-schemas.md)). Begin with
70
- [`AGENTS.md`](AGENTS.md), then [`governance/charter.md`](governance/charter.md) and
71
- [`governance/workflow.md`](governance/workflow.md).
72
-
73
- ## Adopt an *existing* repository
74
-
75
- For a project that already has CODEOWNERS, CI workflows, and nested `AGENTS.md` contracts,
76
- `adopt` maps those existing signals into RepoPact records without overwriting anything:
77
-
78
- ```powershell
79
- repopact adopt --target ../existing-repo --dry-run # preview the plan, write nothing
80
- repopact adopt --target ../existing-repo # create records, then validate
81
- repopact doctor # diagnose + repair drift; migrate on upgrade
82
- ```
83
-
84
- CODEOWNERS becomes scopes and roles; each `.github/workflows/*` becomes a binding-gate policy
85
- (plus invariant `INV-2` and a frozen-surface entry); every nested `AGENTS.md` is registered as
86
- a contract; git history seeds a first **inferred** evidence run, and the adoption record is
87
- recorded as **provisional** — honestly typed, not a fabricated "completed" claim. Adoption is
88
- idempotent.
89
-
90
- ## 2.0: mandatory preflight + provenance-typed records
91
-
92
- Decision [`0021`](decisions/0021-preflight-mandatory-and-provenance.md) (supersedes 0018):
93
-
94
- - **Mandatory preflight (default on).** No work begins until a work item exists and
95
- propagates through the pact; `repopact new` stamps the marker. Existing repos grandfather
96
- their pre-2.0 items automatically `adopt`/`init` set a preflight epoch and `doctor`
97
- migrates on upgrade. *This is a breaking change*: run `repopact doctor` after upgrading.
98
- - **Provenance typing** (`concrete` / `provisional` / `inferred`, default `concrete`). This
99
- is the principled escape from the *adoption trilemma*: `adopt` emits provisional/inferred
100
- records so the result is both **valid** and **faithful** (reconstruction is labelled, not
101
- faked). Completion still requires `concrete` evidence; `doctor` ratchets when it arrives.
102
-
103
- ## Status is a filesystem transition
104
-
105
- Work moves between `work/active`, `work/blocked`, `work/deferred`, and `work/completed`; its
106
- `work-item.json` status must match its directory. Moving a work item never deletes its
107
- reasoning, decisions, or evidence links.
108
-
109
- ## Derive over declare
110
-
111
- Anything computable from source records is generated, not authored by hand (the dashboard,
112
- audit-freshness views, and the derived blocks of `SPEC.md`). Only genuine sources are
113
- hand-maintained. See policy `001`.
114
-
115
- ## Evaluation, formal model & the Proving Ground
116
-
117
- RepoPact is developed against its own evidence, not assertion. The `research/` lab notebook
118
- holds a [formal model](research/formal-model.md) (the L0–L5 kernel, the typed invariant
119
- lattice, the adoption trilemma), the pre-registered [experiment protocol](research/protocol.md)
120
- and [benchmark protocol](research/benchmark-protocol.md) (hypotheses H1–H13, falsification
121
- criteria, [threats to validity](research/threats-to-validity.md)), a [findings
122
- register](research/findings.md), and a full [paper draft](research/paper.md).
123
-
124
- **PactBench** the runnable benchmark suite (pre-registered tasks measuring whether RepoPact
125
- enforcement reduces silent guarantee drift, with a model-agnostic harness and an S5 drift
126
- harness) lives in the **[RepoPact Proving
127
- Ground](https://github.com/ForgeWireLabs/repopact-proving-ground)**, a throwaway-but-real
128
- project that consumes RepoPact from PyPI and is driven across every primitive, including cases
129
- designed to break it. RepoPact defines the *protocol* (`research/`); the Proving Ground hosts
130
- the runnable suite. *RepoPact defines the pact; the Proving Ground tests whether the pact
131
- holds under agent pressure.*
132
-
133
- ## Ecosystem
134
-
135
- RepoPact is the work-governance layer of [ForgeWire Labs](https://github.com/ForgeWireLabs)
136
- inspectable agentic infrastructure (*inspect the work, bound the authority, preserve the
137
- evidence*). It composes with Fabric (execution governance) and ForgeLink (human-agent
138
- communication governance), but is independently useful on its own.
139
-
140
- ## License & version
141
-
142
- Apache-2.0 ([`LICENSE`](LICENSE), decision [`0002`](decisions/0002-license-apache-2.0.md)).
143
- The spec version is in [`VERSION`](VERSION); templates for every record type live in
144
- [`templates/`](templates/).
1
+ # RepoPact
2
+
3
+ RepoPact is a **repository-native governance kernel for durable agent work**. It keeps the
4
+ load-bearing state of a project — intent, authority, evidence, decisions, and drift — as
5
+ typed, version-controlled records in the filesystem, so a new contributor or agent can
6
+ recover where things stand *without* a prior conversation, and so the guarantees that matter
7
+ cannot be silently weakened.
8
+
9
+ > The repository is the pact: authority, intent, evidence, and history that survive every
10
+ > session.
11
+
12
+ `pip install repopact` · Apache-2.0 · current release **2.1.0**
13
+ ([changelog](decisions/0024-release-2.1.0-proposed-lifecycle.md)).
14
+
15
+ ## How it relates to `AGENTS.md`
16
+
17
+ `AGENTS.md` (and `CLAUDE.md`, editor rules) tell an agent *what to do* — they're
18
+ instructions, plain Markdown, with no enforcement. RepoPact is the layer **above** them:
19
+
20
+ > `AGENTS.md` tells an agent how to behave. RepoPact enforces and records whether the work respected the contract.
21
+
22
+ RepoPact's distinguishing primitive is the **binding invariant** — a declared guarantee with
23
+ a rationale, an escalation path, and (where its logical type permits) a machine enforcer.
24
+ That, plus evidence-gated completion and a filesystem state machine, is what turns a folder
25
+ convention into a contract. `repopact adopt` ingests an existing `AGENTS.md` rather than
26
+ replacing it (decision [`0020`](decisions/0020-launch-positioning-layer-above-agents-md.md)).
27
+
28
+ ## Core loop
29
+
30
+ ```text
31
+ intent -> scoped authority -> work item -> implementation -> evidence -> audit -> history
32
+ ```
33
+
34
+ ![RepoPact core loop over a filesystem state machine](docs/assets/repopact-flow.svg)
35
+
36
+ ## Primitives
37
+
38
+ 1. **Charter & invariants** — principles (judgment) and binding invariants
39
+ (escalation-gated) in `governance/`.
40
+ 2. **Frozen surface** — paths and symbols that require operator approval (`--ack`) to change.
41
+ 3. **Scopes & roles** — layered `AGENTS.md` contracts plus a role/scope map in
42
+ `governance/owners.json`.
43
+ 4. **Work items** — narrative `README.md` + machine-readable `work-item.json` with
44
+ evidence-linked acceptance criteria. **Mandatory preflight** (2.0): a work item must be
45
+ recorded *before* implementation begins (`repopact new` stamps the marker).
46
+ 5. **Evidence** — immutable run manifests under `evidence/runs/`.
47
+ 6. **Decisions & policies** — durable choices (`decisions/`) and operating rules
48
+ (`governance/policies/`) whose rationale outlives any single work item.
49
+ 7. **Provenance** (2.0) — every record is `concrete`, `provisional`, or `inferred`.
50
+ `adopt` emits provisional/inferred records (honest, not fabricated); `doctor` ratchets
51
+ them to `concrete` as real evidence arrives. See *2.0 changes* below.
52
+ 8. **Reconciliation** — audits and a *generated* dashboard surface drift and review
53
+ staleness rather than hand-maintaining it.
54
+
55
+ ## Install & quick start
56
+
57
+ ```powershell
58
+ pip install repopact # the CLI + reference validator, from PyPI
59
+ repopact init --target ../your-repo # seed a valid RepoPact in a new repo
60
+ cd ../your-repo
61
+ repopact new work-item "Title of the work" # stamps active work (incl. the preflight marker)
62
+ repopact new work-item "Candidate idea" --status proposed
63
+ repopact validate
64
+ repopact dashboard
65
+ ```
66
+
67
+ `repopact` dispatches `init`, `adopt`, `validate`, `new`, `dashboard`, `spec`,
68
+ `check-frozen`, `import-plan`, and `doctor`. Records are validated against `schemas/*.json`
69
+ (structure) and by the validator (cross-record semantics; decision
70
+ [`0003`](decisions/0003-validate-records-against-json-schemas.md)). Begin with
71
+ [`AGENTS.md`](AGENTS.md), then [`governance/charter.md`](governance/charter.md) and
72
+ [`governance/workflow.md`](governance/workflow.md).
73
+
74
+ Alternative implementations can run the published conformance suite:
75
+
76
+ ```powershell
77
+ python scripts/run_conformance.py --command "your-validator --root {repo}"
78
+ ```
79
+
80
+ See [`CONFORMANCE.md`](CONFORMANCE.md) and [`conformance/`](conformance/).
81
+
82
+ ## Adopt an *existing* repository
83
+
84
+ For a project that already has CODEOWNERS, CI workflows, and nested `AGENTS.md` contracts,
85
+ `adopt` maps those existing signals into RepoPact records without overwriting anything:
86
+
87
+ ```powershell
88
+ repopact adopt --target ../existing-repo --dry-run # preview the plan, write nothing
89
+ repopact adopt --target ../existing-repo # create records, then validate
90
+ repopact doctor # diagnose + repair drift; migrate on upgrade
91
+ ```
92
+
93
+ CODEOWNERS becomes scopes and roles; each `.github/workflows/*` becomes a binding-gate policy
94
+ (plus invariant `INV-2` and a frozen-surface entry); every nested `AGENTS.md` is registered as
95
+ a contract; git history seeds a first **inferred** evidence run, and the adoption record is
96
+ recorded as **provisional**honestly typed, not a fabricated "completed" claim. Adoption is
97
+ idempotent.
98
+
99
+ ## 2.0: mandatory preflight + provenance-typed records
100
+
101
+ Decision [`0021`](decisions/0021-preflight-mandatory-and-provenance.md) (supersedes 0018):
102
+
103
+ - **Mandatory preflight (default on).** No work begins until a work item exists and
104
+ propagates through the pact; `repopact new` stamps the marker. Existing repos grandfather
105
+ their pre-2.0 items automatically `adopt`/`init` set a preflight epoch and `doctor`
106
+ migrates on upgrade. *This is a breaking change*: run `repopact doctor` after upgrading.
107
+ - **Provenance typing** (`concrete` / `provisional` / `inferred`, default `concrete`). This
108
+ is the principled escape from the *adoption trilemma*: `adopt` emits provisional/inferred
109
+ records so the result is both **valid** and **faithful** (reconstruction is labelled, not
110
+ faked). Completion still requires `concrete` evidence; `doctor` ratchets when it arrives.
111
+
112
+ ## Status is a filesystem transition
113
+
114
+ Work moves between lifecycle directories; its `work-item.json` status must match its
115
+ directory. Moving a work item never deletes its reasoning, decisions, or evidence links.
116
+
117
+ - `proposed`: captured candidate work that is not yet accepted or authorized for
118
+ implementation.
119
+ - `active`: accepted work authorized for design or implementation.
120
+ - `blocked`: accepted/current work that cannot proceed until a named condition changes.
121
+ - `deferred`: accepted work intentionally postponed with rationale.
122
+ - `completed`: delivered work whose acceptance criteria are evidence-closed.
123
+
124
+ Active and completed work cannot depend on proposed work as if it were accepted.
125
+
126
+ ## Derive over declare
127
+
128
+ Anything computable from source records is generated, not authored by hand (the dashboard,
129
+ audit-freshness views, and the derived blocks of `SPEC.md`). Only genuine sources are
130
+ hand-maintained. See policy `001`.
131
+
132
+ ## Evaluation, formal model & the Proving Ground
133
+
134
+ RepoPact is developed against its own evidence, not assertion. The `research/` lab notebook
135
+ holds a [formal model](research/formal-model.md) (the L0–L5 kernel, the typed invariant
136
+ lattice, the adoption trilemma), the pre-registered [experiment protocol](research/protocol.md)
137
+ and [benchmark protocol](research/benchmark-protocol.md) (hypotheses H1–H13, falsification
138
+ criteria, [threats to validity](research/threats-to-validity.md)), a [findings
139
+ register](research/findings.md), and the current [paper](research/paper.md).
140
+
141
+ **PactBench** — the runnable benchmark suite (pre-registered tasks measuring whether RepoPact
142
+ enforcement reduces silent guarantee drift, with a model-agnostic harness and an S5 drift
143
+ harness) lives in the **[RepoPact Proving
144
+ Ground](https://github.com/ForgeWireLabs/repopact-proving-ground)**, a throwaway-but-real
145
+ project that consumes RepoPact from PyPI and is driven across every primitive, including cases
146
+ designed to break it. RepoPact defines the *protocol* (`research/`); the Proving Ground hosts
147
+ the runnable suite. *RepoPact defines the pact; the Proving Ground tests whether the pact
148
+ holds under agent pressure.*
149
+
150
+ ## Ecosystem
151
+
152
+ RepoPact is the work-governance layer of [ForgeWire Labs](https://github.com/ForgeWireLabs) —
153
+ inspectable agentic infrastructure (*inspect the work, bound the authority, preserve the
154
+ evidence*). It composes with Fabric (execution governance) and ForgeLink (human-agent
155
+ communication governance), but is independently useful on its own.
156
+
157
+ ## License & version
158
+
159
+ Apache-2.0 ([`LICENSE`](LICENSE), decision [`0002`](decisions/0002-license-apache-2.0.md)).
160
+ The spec version is in [`VERSION`](VERSION); templates for every record type live in
161
+ [`templates/`](templates/).
repopact-2.1.0/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.1.0
@@ -31,10 +31,11 @@ py-modules = [
31
31
  "track_import",
32
32
  "takeover",
33
33
  "doctor",
34
- "new",
35
- "check_frozen_surface",
36
- "frontmatter",
37
- ]
34
+ "new",
35
+ "check_frozen_surface",
36
+ "frontmatter",
37
+ "run_conformance",
38
+ ]
38
39
 
39
40
  [tool.setuptools.package-dir]
40
41
  "" = "scripts"
@@ -45,5 +46,5 @@ version = { file = "VERSION" }
45
46
  # Seed content shipped so `repopact init` works from an installed wheel. These
46
47
  # reference the canonical top-level files (one source of truth — no duplication).
47
48
  [tool.setuptools.data-files]
48
- "share/repopact/schemas" = ["schemas/*.json"]
49
- "share/repopact/templates" = ["templates/*"]
49
+ "share/repopact/schemas" = ["schemas/*.json"]
50
+ "share/repopact/templates" = ["templates/*"]
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "conformance-manifest.schema.json",
4
+ "type": "object",
5
+ "required": ["suite_version", "fixtures_root", "cases"],
6
+ "properties": {
7
+ "suite_version": {"type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"},
8
+ "spec_version_file": {"type": "string"},
9
+ "fixtures_root": {"type": "string", "minLength": 1},
10
+ "cases": {
11
+ "type": "array",
12
+ "minItems": 1,
13
+ "items": {
14
+ "type": "object",
15
+ "required": ["id", "path", "expect", "rules", "description"],
16
+ "properties": {
17
+ "id": {"type": "string", "minLength": 1},
18
+ "path": {"type": "string", "minLength": 1},
19
+ "overlay_on": {"type": "string", "minLength": 1},
20
+ "expect": {"enum": ["accept", "reject"]},
21
+ "expected_message": {"type": "string"},
22
+ "rules": {"type": "array", "items": {"type": "string"}, "minItems": 1},
23
+ "description": {"type": "string", "minLength": 1}
24
+ },
25
+ "allOf": [
26
+ {
27
+ "if": {"properties": {"expect": {"const": "reject"}}},
28
+ "then": {"required": ["expected_message", "overlay_on"]}
29
+ }
30
+ ]
31
+ }
32
+ }
33
+ }
34
+ }
@@ -6,7 +6,7 @@
6
6
  "properties": {
7
7
  "id": {"type": "string", "pattern": "^[0-9]{3,}$"},
8
8
  "title": {"type": "string", "minLength": 1},
9
- "status": {"enum": ["active", "blocked", "deferred", "completed"]},
9
+ "status": {"enum": ["proposed", "active", "blocked", "deferred", "completed"]},
10
10
  "owner_scope": {"type": "string", "minLength": 1},
11
11
  "affected_scopes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
12
12
  "depends_on": {"type": "array", "items": {"type": "string", "pattern": "^[0-9]{3,}$"}, "uniqueItems": true},
@@ -39,4 +39,3 @@
39
39
  "updated": {"type": "string", "format": "date"}
40
40
  }
41
41
  }
42
-
@@ -11,13 +11,14 @@ from __future__ import annotations
11
11
  import argparse
12
12
  import json
13
13
  import shutil
14
+ import site
14
15
  import sys
15
16
  from datetime import date, timedelta
16
17
  from pathlib import Path
17
18
 
18
19
  HERE = Path(__file__).resolve().parent # directory holding the tooling modules
19
20
  CHECKOUT = HERE.parent # repo root when running from a checkout
20
- LIFECYCLE = ("active", "blocked", "deferred", "completed")
21
+ LIFECYCLE = ("proposed", "active", "blocked", "deferred", "completed")
21
22
  MODULES = (
22
23
  "repo_model.py", "validate_repo.py", "generate_dashboard.py", "generate_spec.py",
23
24
  "init_repo.py", "new.py", "check_frozen_surface.py", "frontmatter.py", "repopact_cli.py",
@@ -26,13 +27,16 @@ MODULES = (
26
27
 
27
28
  def _seed_dir(name: str) -> Path:
28
29
  """Locate seed content (schemas/templates) in a checkout or an installed wheel."""
29
- checkout = CHECKOUT / name
30
- if checkout.is_dir():
31
- return checkout
32
- installed = Path(sys.prefix) / "share" / "repopact" / name
33
- if installed.is_dir():
34
- return installed
35
- raise FileNotFoundError(f"cannot locate seed '{name}' (looked in {checkout} and {installed})")
30
+ candidates = [
31
+ CHECKOUT / name,
32
+ Path(sys.prefix) / "share" / "repopact" / name,
33
+ Path(site.USER_BASE) / "share" / "repopact" / name,
34
+ ]
35
+ for candidate in candidates:
36
+ if candidate.is_dir():
37
+ return candidate
38
+ looked = ", ".join(str(candidate) for candidate in candidates)
39
+ raise FileNotFoundError(f"cannot locate seed '{name}' (looked in {looked})")
36
40
 
37
41
 
38
42
  def _write(path: Path, text: str) -> None:
@@ -16,6 +16,8 @@ import sys
16
16
  from datetime import date, datetime
17
17
  from pathlib import Path
18
18
 
19
+ from repo_model import STATUSES
20
+
19
21
  ROOT = Path(__file__).resolve().parents[1]
20
22
 
21
23
 
@@ -32,11 +34,13 @@ def _next_numeric(paths: list[Path], width: int, start: int) -> str:
32
34
  return str(max(used + [start - 1]) + 1).zfill(width)
33
35
 
34
36
 
35
- def new_work_item(title: str, today: date, root: Path = ROOT) -> Path:
37
+ def new_work_item(title: str, today: date, root: Path = ROOT, status: str = "active") -> Path:
38
+ if status not in STATUSES:
39
+ raise ValueError(f"unknown work-item status '{status}'")
36
40
  slug = slugify(title)
37
41
  existing = list((root / "work").glob("*/*/work-item.json"))
38
42
  item_id = _next_numeric([p.parent for p in existing], 3, 1)
39
- directory = root / "work" / "active" / f"{item_id}-{slug}"
43
+ directory = root / "work" / status / f"{item_id}-{slug}"
40
44
  directory.mkdir(parents=True)
41
45
  # Mandatory preflight (decision 0021): creating the work item with `new` IS the
42
46
  # preflight act, so stamp the marker and concrete provenance up front. No work
@@ -44,7 +48,7 @@ def new_work_item(title: str, today: date, root: Path = ROOT) -> Path:
44
48
  created_at = datetime.combine(today, datetime.min.time()).isoformat() + "Z"
45
49
  manifest = {
46
50
  "$schema": "../../../schemas/work-item.schema.json",
47
- "id": item_id, "title": title, "status": "active",
51
+ "id": item_id, "title": title, "status": status,
48
52
  "owner_scope": "governance", "affected_scopes": [], "depends_on": [],
49
53
  "provenance": "concrete",
50
54
  "preflight": {
@@ -83,10 +87,12 @@ def main() -> int:
83
87
  parser = argparse.ArgumentParser(description="Stamp a new record from a template")
84
88
  parser.add_argument("kind", choices=["work-item", "decision", "policy"])
85
89
  parser.add_argument("title")
90
+ parser.add_argument("--status", choices=STATUSES, default="active",
91
+ help="Lifecycle status for a new work item")
86
92
  args = parser.parse_args()
87
93
  today = date.today()
88
94
  if args.kind == "work-item":
89
- path = new_work_item(args.title, today)
95
+ path = new_work_item(args.title, today, status=args.status)
90
96
  else:
91
97
  path = new_markdown(args.kind, args.title, today)
92
98
  print(f"Created {path.relative_to(ROOT)}")
@@ -11,7 +11,7 @@ Supported sources (extensible):
11
11
 
12
12
  * **Plan directories** — `todos/`, `todo/`, `tasks/`, `plan/`, `planning/`, `backlog/`
13
13
  with one subdirectory or markdown file per item, optionally grouped under
14
- `completed/`, `deferred/`, `blocked/`, `active/` lifecycle folders.
14
+ `proposed/`, `completed/`, `deferred/`, `blocked/`, `active/` lifecycle folders.
15
15
  * **Checklist files** — `TODO.md`, `TODOS.md`, `ROADMAP.md`, `BACKLOG.md`, `PLAN.md`,
16
16
  `TASKS.md` at the root: each `- [ ]` / `- [x]` line becomes a work item.
17
17
 
@@ -33,16 +33,23 @@ import adopt_repo # reuse Report and _slug
33
33
 
34
34
  PLAN_DIR_NAMES = ("todos", "todo", "tasks", "plan", "planning", "backlog")
35
35
  CHECKLIST_FILES = ("TODO.md", "TODOS.md", "ROADMAP.md", "BACKLOG.md", "PLAN.md", "TASKS.md")
36
- LIFECYCLE_DIRS = {"completed", "deferred", "blocked", "active"}
36
+ LIFECYCLE_DIRS = {"proposed", "completed", "deferred", "blocked", "active"}
37
37
 
38
38
  # source lifecycle -> (work status, acceptance-criterion state)
39
39
  _STATUS = {
40
+ "proposed": ("proposed", "pending"),
40
41
  "completed": ("completed", "waived"),
41
42
  "deferred": ("deferred", "pending"),
42
43
  "blocked": ("blocked", "pending"),
43
44
  "active": ("active", "pending"),
44
45
  }
45
- _EMOJI = {"completed": "✅ Complete", "deferred": "⏸️ Deferred", "blocked": "⛔ Blocked", "active": "📋 Active"}
46
+ _EMOJI = {
47
+ "proposed": "Proposed",
48
+ "completed": "✅ Complete",
49
+ "deferred": "⏸️ Deferred",
50
+ "blocked": "⛔ Blocked",
51
+ "active": "📋 Active",
52
+ }
46
53
 
47
54
  _SKIP_DIRS = {".git", ".venv", "node_modules", "__pycache__"}
48
55
  # Index/template/readme files in a plan directory are scaffolding, not plan items.
@@ -55,7 +62,7 @@ def _is_scaffold(name: str) -> bool:
55
62
 
56
63
  @dataclass
57
64
  class PlanItem:
58
- lifecycle: str # completed | deferred | blocked | active
65
+ lifecycle: str # proposed | completed | deferred | blocked | active
59
66
  slug: str # RepoPact slug (no leading number)
60
67
  title: str
61
68
  narrative: str # markdown body to preserve
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from typing import Any
7
7
 
8
8
 
9
- STATUSES = ("active", "blocked", "deferred", "completed")
9
+ STATUSES = ("proposed", "active", "blocked", "deferred", "completed")
10
10
 
11
11
  # Directories that are not part of the governed tree: tooling caches and, notably,
12
12
  # test fixtures, which are self-contained sub-repositories validated on their own.
@@ -64,4 +64,3 @@ def discover_evidence_ids(root: Path) -> set[str]:
64
64
  for path in sorted((root / "evidence" / "runs").glob("*.json")):
65
65
  result.add(str(load_json(path).get("id", "")))
66
66
  return result
67
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: repopact
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: A repository-native standard for durable agent work: binding invariants, evidence-gated work items, and drift audits.
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/ForgeWireLabs/repopact
@@ -22,15 +22,15 @@ cannot be silently weakened.
22
22
  > The repository is the pact: authority, intent, evidence, and history that survive every
23
23
  > session.
24
24
 
25
- `pip install repopact` · Apache-2.0 · current release **2.0.1**
26
- ([changelog](decisions/0021-preflight-mandatory-and-provenance.md)).
25
+ `pip install repopact` · Apache-2.0 · current release **2.1.0**
26
+ ([changelog](decisions/0024-release-2.1.0-proposed-lifecycle.md)).
27
27
 
28
28
  ## How it relates to `AGENTS.md`
29
29
 
30
30
  `AGENTS.md` (and `CLAUDE.md`, editor rules) tell an agent *what to do* — they're
31
31
  instructions, plain Markdown, with no enforcement. RepoPact is the layer **above** them:
32
32
 
33
- > `AGENTS.md` tells the agent what to do. RepoPact proves it didn't quietly undo it.
33
+ > `AGENTS.md` tells an agent how to behave. RepoPact enforces and records whether the work respected the contract.
34
34
 
35
35
  RepoPact's distinguishing primitive is the **binding invariant** — a declared guarantee with
36
36
  a rationale, an escalation path, and (where its logical type permits) a machine enforcer.
@@ -71,7 +71,8 @@ intent -> scoped authority -> work item -> implementation -> evidence -> audit -
71
71
  pip install repopact # the CLI + reference validator, from PyPI
72
72
  repopact init --target ../your-repo # seed a valid RepoPact in a new repo
73
73
  cd ../your-repo
74
- repopact new work-item "Title of the work" # stamps records (incl. the preflight marker)
74
+ repopact new work-item "Title of the work" # stamps active work (incl. the preflight marker)
75
+ repopact new work-item "Candidate idea" --status proposed
75
76
  repopact validate
76
77
  repopact dashboard
77
78
  ```
@@ -83,6 +84,14 @@ repopact dashboard
83
84
  [`AGENTS.md`](AGENTS.md), then [`governance/charter.md`](governance/charter.md) and
84
85
  [`governance/workflow.md`](governance/workflow.md).
85
86
 
87
+ Alternative implementations can run the published conformance suite:
88
+
89
+ ```powershell
90
+ python scripts/run_conformance.py --command "your-validator --root {repo}"
91
+ ```
92
+
93
+ See [`CONFORMANCE.md`](CONFORMANCE.md) and [`conformance/`](conformance/).
94
+
86
95
  ## Adopt an *existing* repository
87
96
 
88
97
  For a project that already has CODEOWNERS, CI workflows, and nested `AGENTS.md` contracts,
@@ -115,9 +124,17 @@ Decision [`0021`](decisions/0021-preflight-mandatory-and-provenance.md) (superse
115
124
 
116
125
  ## Status is a filesystem transition
117
126
 
118
- Work moves between `work/active`, `work/blocked`, `work/deferred`, and `work/completed`; its
119
- `work-item.json` status must match its directory. Moving a work item never deletes its
120
- reasoning, decisions, or evidence links.
127
+ Work moves between lifecycle directories; its `work-item.json` status must match its
128
+ directory. Moving a work item never deletes its reasoning, decisions, or evidence links.
129
+
130
+ - `proposed`: captured candidate work that is not yet accepted or authorized for
131
+ implementation.
132
+ - `active`: accepted work authorized for design or implementation.
133
+ - `blocked`: accepted/current work that cannot proceed until a named condition changes.
134
+ - `deferred`: accepted work intentionally postponed with rationale.
135
+ - `completed`: delivered work whose acceptance criteria are evidence-closed.
136
+
137
+ Active and completed work cannot depend on proposed work as if it were accepted.
121
138
 
122
139
  ## Derive over declare
123
140
 
@@ -132,7 +149,7 @@ holds a [formal model](research/formal-model.md) (the L0–L5 kernel, the typed
132
149
  lattice, the adoption trilemma), the pre-registered [experiment protocol](research/protocol.md)
133
150
  and [benchmark protocol](research/benchmark-protocol.md) (hypotheses H1–H13, falsification
134
151
  criteria, [threats to validity](research/threats-to-validity.md)), a [findings
135
- register](research/findings.md), and a full [paper draft](research/paper.md).
152
+ register](research/findings.md), and the current [paper](research/paper.md).
136
153
 
137
154
  **PactBench** — the runnable benchmark suite (pre-registered tasks measuring whether RepoPact
138
155
  enforcement reduces silent guarantee drift, with a model-agnostic harness and an S5 drift
@@ -3,6 +3,7 @@ README.md
3
3
  VERSION
4
4
  pyproject.toml
5
5
  schemas/audit-finding.schema.json
6
+ schemas/conformance-manifest.schema.json
6
7
  schemas/evidence-run.schema.json
7
8
  schemas/frozen-surface.schema.json
8
9
  schemas/invariants.schema.json
@@ -19,6 +20,7 @@ scripts/new.py
19
20
  scripts/plan_import.py
20
21
  scripts/repo_model.py
21
22
  scripts/repopact_cli.py
23
+ scripts/run_conformance.py
22
24
  scripts/takeover.py
23
25
  scripts/track_import.py
24
26
  scripts/validate_repo.py
@@ -9,6 +9,7 @@ new
9
9
  plan_import
10
10
  repo_model
11
11
  repopact_cli
12
+ run_conformance
12
13
  takeover
13
14
  track_import
14
15
  validate_repo
@@ -10,8 +10,10 @@ from __future__ import annotations
10
10
 
11
11
  import argparse
12
12
  import sys
13
- from datetime import date
14
- from pathlib import Path
13
+ from datetime import date
14
+ from pathlib import Path
15
+
16
+ from repo_model import STATUSES
15
17
 
16
18
 
17
19
  def main(argv: list[str] | None = None) -> int:
@@ -50,10 +52,12 @@ def main(argv: list[str] | None = None) -> int:
50
52
  p_spec = sub.add_parser("spec", help="Regenerate SPEC.md derived blocks")
51
53
  p_spec.add_argument("--root", type=Path, default=Path.cwd())
52
54
 
53
- p_new = sub.add_parser("new", help="Stamp a new record from a template")
54
- p_new.add_argument("kind", choices=["work-item", "decision", "policy"])
55
- p_new.add_argument("title")
56
- p_new.add_argument("--root", type=Path, default=Path.cwd())
55
+ p_new = sub.add_parser("new", help="Stamp a new record from a template")
56
+ p_new.add_argument("kind", choices=["work-item", "decision", "policy"])
57
+ p_new.add_argument("title")
58
+ p_new.add_argument("--root", type=Path, default=Path.cwd())
59
+ p_new.add_argument("--status", choices=STATUSES, default="active",
60
+ help="Lifecycle status for a new work item")
57
61
 
58
62
  p_frz = sub.add_parser("check-frozen", help="Report frozen-surface changes in a diff range")
59
63
  p_frz.add_argument("--root", type=Path, default=Path.cwd())
@@ -169,10 +173,10 @@ def main(argv: list[str] | None = None) -> int:
169
173
  print("Generated SPEC.md derived blocks")
170
174
  return 0
171
175
 
172
- if args.command == "new":
173
- import new
174
- if args.kind == "work-item":
175
- path = new.new_work_item(args.title, date.today(), root)
176
+ if args.command == "new":
177
+ import new
178
+ if args.kind == "work-item":
179
+ path = new.new_work_item(args.title, date.today(), root, status=args.status)
176
180
  else:
177
181
  path = new.new_markdown(args.kind, args.title, date.today(), root)
178
182
  print(f"Created {path.relative_to(root)}")
@@ -0,0 +1,112 @@
1
+ """Run the published RepoPact conformance suite.
2
+
3
+ The runner materializes each fixture as an isolated temporary repository, injects
4
+ the canonical schemas from this checkout, runs a RepoPact implementation, and
5
+ compares the result with conformance/manifest.json.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import json
12
+ import shutil
13
+ import subprocess
14
+ import sys
15
+ import tempfile
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+
19
+
20
+ ROOT = Path(__file__).resolve().parents[1]
21
+ MANIFEST = ROOT / "conformance" / "manifest.json"
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class CaseResult:
26
+ case_id: str
27
+ passed: bool
28
+ detail: str
29
+
30
+
31
+ def load_manifest(path: Path = MANIFEST) -> dict:
32
+ with path.open(encoding="utf-8") as handle:
33
+ data = json.load(handle)
34
+ if not isinstance(data, dict):
35
+ raise ValueError(f"manifest must be a JSON object: {path}")
36
+ return data
37
+
38
+
39
+ def _copy_overlay(src: Path, dst: Path) -> None:
40
+ for path in src.rglob("*"):
41
+ if path.is_dir() or path.name == "meta.json":
42
+ continue
43
+ target = dst / path.relative_to(src)
44
+ target.parent.mkdir(parents=True, exist_ok=True)
45
+ shutil.copy2(path, target)
46
+
47
+
48
+ def materialize_case(root: Path, fixtures_root: Path, case: dict) -> Path:
49
+ repo = root / "repo"
50
+ overlay_on = case.get("overlay_on")
51
+ if overlay_on:
52
+ shutil.copytree(fixtures_root / str(overlay_on), repo)
53
+ _copy_overlay(fixtures_root / str(case["path"]), repo)
54
+ else:
55
+ shutil.copytree(fixtures_root / str(case["path"]), repo)
56
+ shutil.copytree(ROOT / "schemas", repo / "schemas")
57
+ return repo
58
+
59
+
60
+ def run_command(command: str, repo: Path) -> subprocess.CompletedProcess[str]:
61
+ rendered = command.format(repo=str(repo))
62
+ return subprocess.run(rendered, shell=True, text=True, capture_output=True)
63
+
64
+
65
+ def evaluate_case(case: dict, command: str, fixtures_root: Path) -> CaseResult:
66
+ case_id = str(case["id"])
67
+ with tempfile.TemporaryDirectory(prefix=f"repopact-conformance-{case_id}-") as tmp:
68
+ repo = materialize_case(Path(tmp), fixtures_root, case)
69
+ proc = run_command(command, repo)
70
+ output = "\n".join(part for part in (proc.stdout, proc.stderr) if part)
71
+ expect = case.get("expect")
72
+ if expect == "accept":
73
+ passed = proc.returncode == 0
74
+ detail = "accepted" if passed else f"expected accept, exit={proc.returncode}: {output.strip()}"
75
+ return CaseResult(case_id, passed, detail)
76
+ expected = str(case.get("expected_message", ""))
77
+ passed = proc.returncode != 0 and expected in output
78
+ detail = (
79
+ f"rejected with '{expected}'"
80
+ if passed
81
+ else f"expected reject containing '{expected}', exit={proc.returncode}: {output.strip()}"
82
+ )
83
+ return CaseResult(case_id, passed, detail)
84
+
85
+
86
+ def run_suite(command: str, manifest_path: Path = MANIFEST) -> list[CaseResult]:
87
+ manifest = load_manifest(manifest_path)
88
+ fixtures_root = manifest_path.parent / str(manifest.get("fixtures_root", "fixtures"))
89
+ return [evaluate_case(case, command, fixtures_root) for case in manifest.get("cases", [])]
90
+
91
+
92
+ def main() -> int:
93
+ parser = argparse.ArgumentParser(description="Run the RepoPact conformance suite")
94
+ parser.add_argument("--manifest", type=Path, default=MANIFEST)
95
+ parser.add_argument(
96
+ "--command",
97
+ default=f'"{sys.executable}" "{ROOT / "scripts" / "validate_repo.py"}" --root "{{repo}}"',
98
+ help="Implementation command template; {repo} is replaced with the fixture repo path.",
99
+ )
100
+ args = parser.parse_args()
101
+
102
+ results = run_suite(args.command, args.manifest.resolve())
103
+ failed = [result for result in results if not result.passed]
104
+ for result in results:
105
+ status = "PASS" if result.passed else "FAIL"
106
+ print(f"{status} {result.case_id}: {result.detail}")
107
+ print(f"\n{len(results) - len(failed)}/{len(results)} conformance cases passed.")
108
+ return 1 if failed else 0
109
+
110
+
111
+ if __name__ == "__main__":
112
+ sys.exit(main())
@@ -197,7 +197,7 @@ def registry_scopes_inside(root: Path, d: Path) -> list[str]:
197
197
  # File types we scan for inbound references; everything else (binaries, build output) is skipped.
198
198
  _TEXT_EXTS = {".md", ".markdown", ".txt", ".rst", ".py", ".toml", ".yaml", ".yml",
199
199
  ".json", ".cfg", ".ini", ".sh", ".ps1"}
200
- _LIFECYCLE = {"completed", "deferred", "active", "blocked"}
200
+ _LIFECYCLE = {"completed", "deferred", "active", "blocked", "proposed"}
201
201
  _MD_LINK = re.compile(r"(\]\()([^)\s]+)(\))")
202
202
 
203
203
 
@@ -381,11 +381,18 @@ def validate_work(root: Path, owner_scopes: set[str], enforce_disjoint: bool, pr
381
381
  validate_work_preflight(item, manifest, preflight_cfg, problems)
382
382
  validate_provenance(item, ev_prov, manifest, problems)
383
383
 
384
- all_ids = set(seen)
385
- for item in items:
386
- for dependency in item.data.get("depends_on", []):
387
- if dependency not in all_ids:
388
- problems.append(Problem(item.directory / "work-item.json", f"unknown dependency '{dependency}'"))
384
+ all_ids = set(seen)
385
+ status_by_id = {item.item_id: item.status for item in items}
386
+ for item in items:
387
+ for dependency in item.data.get("depends_on", []):
388
+ if dependency not in all_ids:
389
+ problems.append(Problem(item.directory / "work-item.json", f"unknown dependency '{dependency}'"))
390
+ elif item.status in ("active", "completed") and status_by_id.get(dependency) == "proposed":
391
+ problems.append(Problem(
392
+ item.directory / "work-item.json",
393
+ f"{item.status} work item depends on proposed work item '{dependency}'; "
394
+ "proposed work is not accepted implementation authority",
395
+ ))
389
396
 
390
397
  detect_dependency_cycles(items, all_ids, problems)
391
398
  if enforce_disjoint:
@@ -1,9 +1,9 @@
1
- """Conformance corpus test (work item 006, issue #4).
1
+ """Conformance corpus test (work items 006 and 019).
2
2
 
3
- Validates the fixtures under tests/fixtures/: the valid baseline must be accepted,
4
- and each invalid overlay must be rejected with its declared message. Fixtures do
5
- not vendor schemas; the canonical schemas/ are injected here so a fixture can never
6
- pass against a stale schema copy (no drift).
3
+ Validates the published suite under conformance/: the valid baseline must be
4
+ accepted, and each invalid overlay must be rejected with its declared message.
5
+ Fixtures do not vendor schemas; the canonical schemas/ are injected here so a
6
+ fixture can never pass against a stale schema copy (no drift).
7
7
  """
8
8
 
9
9
  from __future__ import annotations
@@ -15,12 +15,17 @@ import tempfile
15
15
  import unittest
16
16
  from pathlib import Path
17
17
 
18
+ import jsonschema
19
+
18
20
  ROOT = Path(__file__).resolve().parents[1]
19
21
  sys.path.insert(0, str(ROOT / "scripts"))
20
22
 
21
23
  from validate_repo import validate # noqa: E402
22
24
 
23
- FIXTURES = ROOT / "tests" / "fixtures"
25
+ import run_conformance # noqa: E402
26
+
27
+ MANIFEST = ROOT / "conformance" / "manifest.json"
28
+ FIXTURES = ROOT / "conformance" / "fixtures"
24
29
  VALID = FIXTURES / "valid"
25
30
  INVALID = FIXTURES / "invalid"
26
31
 
@@ -39,6 +44,22 @@ def build_repo(dst: Path, overlay: Path | None = None) -> Path:
39
44
 
40
45
 
41
46
  class ConformanceTests(unittest.TestCase):
47
+ def test_manifest_is_structurally_valid(self) -> None:
48
+ manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
49
+ schema = json.loads((ROOT / "schemas" / "conformance-manifest.schema.json").read_text(encoding="utf-8"))
50
+ jsonschema.Draft202012Validator(schema).validate(manifest)
51
+ self.assertEqual((ROOT / "VERSION").read_text(encoding="utf-8").strip(), manifest["suite_version"])
52
+ for case in manifest["cases"]:
53
+ self.assertTrue((FIXTURES / case["path"]).exists(), case["path"])
54
+
55
+ def test_manifest_matches_reference_suite(self) -> None:
56
+ results = run_conformance.run_suite(
57
+ f'"{sys.executable}" "{ROOT / "scripts" / "validate_repo.py"}" --root "{{repo}}"',
58
+ MANIFEST,
59
+ )
60
+ self.assertTrue(results, "no conformance cases found")
61
+ self.assertEqual([], [result.detail for result in results if not result.passed])
62
+
42
63
  def test_valid_fixture_is_accepted(self) -> None:
43
64
  with tempfile.TemporaryDirectory() as tmp:
44
65
  repo = build_repo(Path(tmp) / "repo")
@@ -48,17 +48,23 @@ class RepositoryValidationTests(unittest.TestCase):
48
48
  mutate(data)
49
49
  path.write_text(json.dumps(data), encoding="utf-8")
50
50
 
51
- def add_active_item(self, item_id: str, owner_scope: str = "work", preflight: bool = True) -> None:
52
- directory = self.root / "work" / "active" / f"{item_id}-probe"
53
- directory.mkdir(parents=True)
54
- (directory / "README.md").write_text("# probe\n", encoding="utf-8")
55
- data = {
56
- "id": item_id,
57
- "title": "probe",
58
- "status": "active",
59
- "owner_scope": owner_scope,
60
- "affected_scopes": [],
61
- "depends_on": [],
51
+ def add_work_item(
52
+ self,
53
+ item_id: str,
54
+ status: str = "active",
55
+ owner_scope: str = "work",
56
+ preflight: bool = True,
57
+ ) -> None:
58
+ directory = self.root / "work" / status / f"{item_id}-probe"
59
+ directory.mkdir(parents=True)
60
+ (directory / "README.md").write_text("# probe\n", encoding="utf-8")
61
+ data = {
62
+ "id": item_id,
63
+ "title": "probe",
64
+ "status": status,
65
+ "owner_scope": owner_scope,
66
+ "affected_scopes": [],
67
+ "depends_on": [],
62
68
  "provenance": "concrete",
63
69
  "acceptance_criteria": [{"id": "AC-1", "text": "x", "state": "pending", "evidence": []}],
64
70
  "created": "2026-06-15",
@@ -69,9 +75,12 @@ class RepositoryValidationTests(unittest.TestCase):
69
75
  "created_before_work_started": True,
70
76
  "created_at": "2026-06-15T00:00:00Z",
71
77
  "note": "probe",
72
- }
73
- (directory / "work-item.json").write_text(json.dumps(data), encoding="utf-8")
74
-
78
+ }
79
+ (directory / "work-item.json").write_text(json.dumps(data), encoding="utf-8")
80
+
81
+ def add_active_item(self, item_id: str, owner_scope: str = "work", preflight: bool = True) -> None:
82
+ self.add_work_item(item_id, "active", owner_scope, preflight)
83
+
75
84
  # --- baseline -----------------------------------------------------------
76
85
 
77
86
  def test_repository_is_valid(self) -> None:
@@ -90,10 +99,27 @@ class RepositoryValidationTests(unittest.TestCase):
90
99
  self.write_json(path, lambda d: (d["acceptance_criteria"][0].update({"state": "satisfied", "evidence": []})))
91
100
  self.assertTrue(any("satisfied without evidence" in v for v in self.problems()))
92
101
 
93
- def test_completed_item_cannot_have_pending_criteria(self) -> None:
94
- path = self.manifest()
95
- self.write_json(path, lambda d: (d["acceptance_criteria"][0].update({"state": "pending", "evidence": []})))
96
- self.assertTrue(any("completed item has pending criterion" in v for v in self.problems()))
102
+ def test_completed_item_cannot_have_pending_criteria(self) -> None:
103
+ path = self.manifest()
104
+ self.write_json(path, lambda d: (d["acceptance_criteria"][0].update({"state": "pending", "evidence": []})))
105
+ self.assertTrue(any("completed item has pending criterion" in v for v in self.problems()))
106
+
107
+ def test_valid_proposed_work_item_validates(self) -> None:
108
+ self.add_work_item("900", status="proposed")
109
+ self.assertEqual([], self.problems())
110
+
111
+ def test_invalid_proposed_work_item_fails_structure_validation(self) -> None:
112
+ self.add_work_item("900", status="proposed")
113
+ path = self.root / "work" / "proposed" / "900-probe" / "work-item.json"
114
+ self.write_json(path, lambda d: d.pop("title"))
115
+ self.assertTrue(any("missing fields: title" in v for v in self.problems()))
116
+
117
+ def test_active_work_cannot_depend_on_proposed_work(self) -> None:
118
+ self.add_work_item("900", status="proposed")
119
+ self.add_active_item("901")
120
+ active = self.root / "work" / "active" / "901-probe" / "work-item.json"
121
+ self.write_json(active, lambda d: d.__setitem__("depends_on", ["900"]))
122
+ self.assertTrue(any("depends on proposed work item '900'" in v for v in self.problems()))
97
123
 
98
124
  def test_readme_checkbox_parity_flags_contradiction(self) -> None:
99
125
  path = self.manifest()
@@ -398,10 +424,11 @@ class RepositoryValidationTests(unittest.TestCase):
398
424
 
399
425
  # --- bootstrap (003 B1) -------------------------------------------------
400
426
 
401
- def test_bootstrap_produces_valid_repo(self) -> None:
402
- target = Path(self.temp.name) / "seeded"
403
- init_repo.bootstrap(target)
404
- self.assertEqual([], [p.message for p in validate(target)])
427
+ def test_bootstrap_produces_valid_repo(self) -> None:
428
+ target = Path(self.temp.name) / "seeded"
429
+ init_repo.bootstrap(target)
430
+ self.assertTrue((target / "work" / "proposed").is_dir())
431
+ self.assertEqual([], [p.message for p in validate(target)])
405
432
 
406
433
  # --- SPEC generator determinism (004) ----------------------------------
407
434
 
@@ -418,12 +445,21 @@ class RepositoryValidationTests(unittest.TestCase):
418
445
  def test_cli_validate_returns_zero_on_valid_repo(self) -> None:
419
446
  self.assertEqual(0, repopact_cli.main(["validate", "--root", str(self.root)]))
420
447
 
421
- def test_cli_new_stamps_a_valid_record(self) -> None:
422
- rc = repopact_cli.main(["new", "work-item", "Cli Probe", "--root", str(self.root)])
423
- self.assertEqual(0, rc)
424
- stamped = list((self.root / "work" / "active").glob("*-cli-probe/work-item.json"))
425
- self.assertEqual(1, len(stamped))
426
- self.assertEqual([], [p.message for p in validate(self.root)])
448
+ def test_cli_new_stamps_a_valid_record(self) -> None:
449
+ rc = repopact_cli.main(["new", "work-item", "Cli Probe", "--root", str(self.root)])
450
+ self.assertEqual(0, rc)
451
+ stamped = list((self.root / "work" / "active").glob("*-cli-probe/work-item.json"))
452
+ self.assertEqual(1, len(stamped))
453
+ self.assertEqual([], [p.message for p in validate(self.root)])
454
+
455
+ def test_cli_new_can_stamp_proposed_work_item(self) -> None:
456
+ rc = repopact_cli.main(["new", "work-item", "Cli Proposal", "--status", "proposed", "--root", str(self.root)])
457
+ self.assertEqual(0, rc)
458
+ stamped = list((self.root / "work" / "proposed").glob("*-cli-proposal/work-item.json"))
459
+ self.assertEqual(1, len(stamped))
460
+ data = json.loads(stamped[0].read_text(encoding="utf-8"))
461
+ self.assertEqual("proposed", data["status"])
462
+ self.assertEqual([], [p.message for p in validate(self.root)])
427
463
 
428
464
  # --- proving-ground hardening (007) -------------------------------------
429
465
 
repopact-2.0.1/VERSION DELETED
@@ -1 +0,0 @@
1
- 2.0.1
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes