From 47141f563c292098ad11f019ea54793b4436716f Mon Sep 17 00:00:00 2001 From: iperev Date: Sun, 20 Sep 2026 16:07:39 +0200 Subject: [PATCH 1/2] feat: clarify specification context and review inputs --- ADOPTION.md | 24 +++ internal/app/change_input_guide_test.go | 14 ++ internal/app/cli_contract_test.go | 2 +- internal/app/command_contract_generated.go | 30 +-- internal/app/command_help.go | 4 + internal/app/context_input_guide_test.go | 174 ++++++++++++++++++ .../command/changeworkflowplan/input_guide.go | 14 +- .../command/requirementcontext/input_guide.go | 88 +++++++++ .../stackpreset/preset_ids_generated.go | 2 +- internal/tools/releasechange/record_test.go | 8 +- package-lock.json | 4 +- package.json | 2 +- proofkit/cli-contract.v2.json | 36 ++-- release/change-record.v2.json | 8 +- 14 files changed, 363 insertions(+), 47 deletions(-) create mode 100644 internal/app/context_input_guide_test.go create mode 100644 internal/command/requirementcontext/input_guide.go diff --git a/ADOPTION.md b/ADOPTION.md index a66d3ec1..72e7b9a6 100644 --- a/ADOPTION.md +++ b/ADOPTION.md @@ -435,6 +435,30 @@ and require an explicit consumer mapping when a selector or wrapper path is ambiguous. Proofkit's public structured graph preserves declared routes; it does not discover native tests or judge their assertions. +Choose scenario storage by the meaning it must own, not by a mandatory extra +document layer: + +| Candidate | Appropriate boundary | Cost or limitation to review | +|---|---|---| +| Separate scenario document | Independently meaningful portable conditions or examples with their own review lifecycle. | Another normative artifact, reference closure and freshness policy; do not repeat the same promise. | +| Scenarios inside the specification | Portable scenario meaning owned with the requirement. | Public source v1 has no typed scenario-body field; do not add unadmitted keys or describe a private candidate as public. | +| Structured native declarations | Stable references, parameter instances and executable expected observations near the native check. | Derive the inventory and require review when assertions or qualified links change; native expectations cannot silently redefine intent. | +| Test-adjacent annotations | References attached to a framework-owned test declaration. | Parse the actual native declaration association; comments alone do not prove discovery, execution or assertion quality. | + +Prefer the existing source plus structured native declarations when it expresses +the required workflow. A separately editable scenario store is not necessary +just to connect IDs. Reconsider it when portable scenario meaning cannot be +expressed without losing a required distinction. This is not a universal format +or human-usability ranking; measure the complete input, edit and maintenance +cost for the consuming repository. + +Use `requirement-context-compose --help` for the connected catalog/tree recipe +and canonical source/binding snapshot. Before committing, a confirmation must +cover the publication plane actually being approved. Checking working files +does not approve different index bytes; `change plan --help` explains this +consumer-owned precondition. Neither command installs a Git hook or grants +approval authority. + Reverse review is distinct from the forward execution route above: ```mermaid diff --git a/internal/app/change_input_guide_test.go b/internal/app/change_input_guide_test.go index 38e3b7c5..9d0d7616 100644 --- a/internal/app/change_input_guide_test.go +++ b/internal/app/change_input_guide_test.go @@ -21,11 +21,25 @@ func TestChangeInputGuideIsLazyAndExecutable(t *testing.T) { for _, boundary := range []string{ "does not read", "Do not hash only IDs", "source-qualified pairs", "required consumer check", "not authenticated approval", "empty prefix reviews architecture", + "working tree, Git index, immutable", "different staged bytes", "executable modes", + "untracked inputs and filter effects", "neither Git inspection", + "requirement-context-compose --help", "excludes undeclared native dependencies", } { if !strings.Contains(help, boundary) { t.Fatalf("guide lost boundary %q", boundary) } } + normalized := strings.Join(strings.Fields(help), " ") + for _, sentence := range []string{ + "Read that exact plane; a working-tree check alone cannot approve different staged bytes.", + "If partial staging is unsupported, require exact index/worktree bytes and executable modes for the complete input scope, including untracked inputs and filter effects.", + "Otherwise materialize and check the selected index/commit separately.", + "Proofkit performs neither Git inspection nor this consumer precondition.", + } { + if !strings.Contains(normalized, sentence) { + t.Fatalf("publication-plane policy changed: %s", sentence) + } + } code, output, diagnostic := executeAgentWorkflowCLI(t, []string{"change", "plan", "--input", "-"}, bytes.NewReader(adoptionHelpJSON(t, packet)), PresentationCapabilities{}) if code != 1 || output != "" || diagnostic == "" { t.Fatal("unfilled template fabricated an admissible assessment") diff --git a/internal/app/cli_contract_test.go b/internal/app/cli_contract_test.go index c729db07..9fbe7915 100644 --- a/internal/app/cli_contract_test.go +++ b/internal/app/cli_contract_test.go @@ -24,7 +24,7 @@ import ( ) const ( - cliContractPublicABISHA256 = "31d82294be58a80cdef00a33a554cfeeb17a03903083ebf56a38a994d7f2def9" + cliContractPublicABISHA256 = "7ff77cfb782eba71fb581e04aa618052a860e430f5a2b3b5d23bccd6c97352eb" maxAggregateFileReadBytesForContractTest = 64 << 20 maxPackageManifestBytesForContractTest = 256 << 10 maxSourceFileBytesForContractTest = 8 << 20 diff --git a/internal/app/command_contract_generated.go b/internal/app/command_contract_generated.go index 7c154214..1749bac2 100644 --- a/internal/app/command_contract_generated.go +++ b/internal/app/command_contract_generated.go @@ -1,7 +1,7 @@ // Code generated by internal/tools/commandcontractgen; DO NOT EDIT. package app -const commandContractSourceSHA256 = "7bb0e36255f4b08eca5f067f133b16b5a35111789ce1a46eccf63adccf2cf72c" +const commandContractSourceSHA256 = "7a4a0bf93a1aca3dd91d84e7c0e6303944d301a665a3649293639c3c85d01fb1" type generatedCommandContractMetadata struct { InputContractSHA256 string @@ -12,19 +12,19 @@ type generatedCommandContractMetadata struct { } var generatedCommandContractMetadataByName = map[string]generatedCommandContractMetadata{ - "adopt-materialize-apply": {InputContractSHA256: "sha256:ff3aee6b2420c04d19afdf3a72ef2a73b731f25cc1abcb0f3fe1ffd0fc9ed06b", InputSchemaSummary: []string{"schemaVersion=1", "owner-admitted adoption plan, requirement sources, proof bindings, and direct test inventory", "root-shape-only definition proofkit.adoption-materialization.apply-input.v1.root-shape; nested fields, types, cardinalities, and cross-record closure remain native-owner claims"}, OutputContractSHA256: "sha256:d9462227e1539ed48afe3ca3df16df427e989ccf060ec46b3a78e76bec4ade48", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"adopt", "materialize", "apply"}}, - "adopt-materialize-plan": {InputContractSHA256: "sha256:484d8d867ff1080d14f14c9b82c9e02ce0bb10ded7713b8098b5a2f5128fefc2", InputSchemaSummary: []string{"schemaVersion=1", "owner-admitted adoption plan, requirement sources, proof bindings, and direct test inventory", "root-shape-only definition proofkit.adoption-materialization.plan-input.v1.root-shape; nested fields, types, cardinalities, and cross-record closure remain native-owner claims"}, OutputContractSHA256: "sha256:465534bc2676a61afae85278b27571879dfc8e18920935f2fc23f8e622061af9", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"adopt", "materialize", "plan"}}, - "adopt-materialize-recover": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:2bbc8a16190cc567296ba1260a7e468465b55e19a7f825044d7ab5b43a884afd", FlagChoices: map[string][]string{"--action": []string{"resume", "rollback"}, "--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"adopt", "materialize", "recover"}}, + "adopt-materialize-apply": {InputContractSHA256: "sha256:ff3aee6b2420c04d19afdf3a72ef2a73b731f25cc1abcb0f3fe1ffd0fc9ed06b", InputSchemaSummary: []string{"schemaVersion=1", "owner-admitted adoption plan, requirement sources, proof bindings, and direct test inventory", "root-shape-only definition proofkit.adoption-materialization.apply-input.v1.root-shape; nested fields, types, cardinalities, and cross-record closure remain native-owner claims"}, OutputContractSHA256: "sha256:4de7430aec27eefd86963f88a4c95ea1fafca199399bad1d6c79a8131e55625b", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"adopt", "materialize", "apply"}}, + "adopt-materialize-plan": {InputContractSHA256: "sha256:484d8d867ff1080d14f14c9b82c9e02ce0bb10ded7713b8098b5a2f5128fefc2", InputSchemaSummary: []string{"schemaVersion=1", "owner-admitted adoption plan, requirement sources, proof bindings, and direct test inventory", "root-shape-only definition proofkit.adoption-materialization.plan-input.v1.root-shape; nested fields, types, cardinalities, and cross-record closure remain native-owner claims"}, OutputContractSHA256: "sha256:24f8ddbb2aac3e9cd352f03c2c25cb06880a746943274a45a2f603601f70e442", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"adopt", "materialize", "plan"}}, + "adopt-materialize-recover": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:359a9562b34f3babbe7c030beb54f5d1faf706ba675b13db74eba04b2bc02d54", FlagChoices: map[string][]string{"--action": []string{"resume", "rollback"}, "--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"adopt", "materialize", "recover"}}, "adopt-plan": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:ba2bb3ce147ac37bde035334e0058820339b36de2a0ae270a9e5dbe00e6a3e6d", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}, "--mode": []string{"audit-from-code", "code-baseline", "fresh"}, "--stack": []string{"agentic_runtime_repo", "generated_docs_contract_repo", "python_service", "python_typescript_service", "typescript_monorepo", "typescript_workspace"}}, RouteTokens: []string{"adopt", "plan"}}, "adoption-checklist": {InputContractSHA256: "sha256:4e6c4c9b369279837a5894c0b3f842a411dce529b91c91cb2d4ec63eb5ee4c2c", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.adoption-checklist.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:9d0d0e60f0935407fd31007d8502459663eb4c7228dc5e3c7727ae2c9907bdc9", FlagChoices: map[string][]string{}, RouteTokens: []string{"adoption-checklist"}}, "adoption-contract-envelope": {InputContractSHA256: "sha256:c310214676ff4b6f536a5bc9d687f681a7e71f73d7a03ac932707d8cd3905cdf", InputSchemaSummary: []string{"schemaVersion=2", "root-shape-only definition proofkit.adoption-contract-envelope.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:3efb2c5161fee16fd8ac6a40dcb6d9c41fbc23e468f60621436ae9e8076e0950", FlagChoices: map[string][]string{}, RouteTokens: []string{"adoption-contract-envelope"}}, "adoption-doctor": {InputContractSHA256: "sha256:efa9acfe32bff07f56d9dc9902530df2979794289bc2f7f547f7a108a7dd0f35", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.adoption-doctor.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:8fdfc6608f197e633f042f20031ae1014872a90aa3daa66885ffcaddca994766", FlagChoices: map[string][]string{}, RouteTokens: []string{"adoption-doctor"}}, "adoption-workflow-plan": {InputContractSHA256: "sha256:b32ae67179d7b6dcf1ea66cb6b2b2691c8367ce2e2be367619b65973166da55c", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.adoption-workflow-plan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:8d64cb53ebd0307e3cebc3435286a3d2a1ee8a0ad6f7514fc0fb3285db0f565b", FlagChoices: map[string][]string{}, RouteTokens: []string{"adoption-workflow-plan"}}, - "agent-route": {InputContractSHA256: "sha256:c00e832b4e9eac6b858eec46e810431c0a5c9f56c5c50f055f39ee024f50014c", InputSchemaSummary: []string{"availableInputs", "browserMode", "goal", "knownChangedPaths", "mode", "nonClaims", "observedReports", "openBrowser", "routeId", "schemaVersion", "root-shape-only definition proofkit.agent-route.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:e59dada1926e3385793cdcb0a8b9cf8110b8f995b785c27ac397b60ba972b571", FlagChoices: map[string][]string{}, RouteTokens: []string{"agent-route"}}, + "agent-route": {InputContractSHA256: "sha256:c00e832b4e9eac6b858eec46e810431c0a5c9f56c5c50f055f39ee024f50014c", InputSchemaSummary: []string{"availableInputs", "browserMode", "goal", "knownChangedPaths", "mode", "nonClaims", "observedReports", "openBrowser", "routeId", "schemaVersion", "root-shape-only definition proofkit.agent-route.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:86cd10c43d93fbe82392a83749c198e3ca754a9b7e7e5f46cba1fc8ee0702e08", FlagChoices: map[string][]string{}, RouteTokens: []string{"agent-route"}}, "binding-partition": {InputContractSHA256: "sha256:366ad082045af52b2ac6604f18626d0f285b2db73b45d9a82687b8d3b0d2b3fd", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.binding-partition.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:52840879e13a00ef9a4abaad6cdb33000511674d5f9003fb56f387fdf58fadc8", FlagChoices: map[string][]string{}, RouteTokens: []string{"binding-partition"}}, "branch-authority": {InputContractSHA256: "sha256:8a3ed74978898593fbdbf1f7fa684dae450fbd9019edcd60d07f818d63363ed4", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.branch-authority.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:3c7dc74842299b92cd5baf57cc8666e9415963091359e5faf654e28da89561f1", FlagChoices: map[string][]string{}, RouteTokens: []string{"branch-authority"}}, "capability-map-admission": {InputContractSHA256: "sha256:36025145e1be04f8da9baccd2161b4ccf04f5426e95084d9cccc01802970e29d", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.capability-map-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:5100e56075f50435605264c6a835a60f24f6790479e2b4c623d359f1e7e78690", FlagChoices: map[string][]string{}, RouteTokens: []string{"capability-map-admission"}}, - "change-workflow-plan": {InputContractSHA256: "sha256:fe64cfbd2f8f9c74ce822aaf7acaabb53b6dcef9f193ac3c25ad79936c4f5afd", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.change-workflow-plan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:c81afa10bb91cd08c36d87ece85113e7acc87a038183ed609c47845ceeafabfd", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"change", "plan"}}, + "change-workflow-plan": {InputContractSHA256: "sha256:375bb9805c2e194519b68850d9dd5d260eaa8d728dd6d9e5bcd3423af0b1fbce", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.change-workflow-plan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:50598d82c871841b7f0e1a70e7b589a44743843c0cf28378056703f63ec0ef56", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"change", "plan"}}, "changed-path-set": {InputContractSHA256: "sha256:8fe97426a58969e3e8dcbd52ed44540666b4de6be0487e8a3bc5088ae9c0f933", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.changed-path-set.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:abccdbf78e67f633ce49c34e8849c03f08ce42fa934a4c68969720c5045bf593", FlagChoices: map[string][]string{}, RouteTokens: []string{"changed-path-set"}}, "completion-criteria": {InputContractSHA256: "sha256:99c49c44b001e40383787e4c55f66621b8a8315f09635f1baf2326dc09bec4e6", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.completion-criteria.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:c90bb9605c7a22914104701a068dded510534bdeb60f4d601555d46c2d3d8a6d", FlagChoices: map[string][]string{}, RouteTokens: []string{"completion-criteria"}}, "conformance-profile": {InputContractSHA256: "sha256:10857de4cea06702bb4d35580046275d4b1f88821d287a4c57dabc187bda954e", InputSchemaSummary: []string{"schemaVersion=2", "root-shape-only definition proofkit.conformance-profile.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:4654015b8b9055080c1d5528773462fab3fe81d40c7f3b9870e5dbec4dc98caf", FlagChoices: map[string][]string{}, RouteTokens: []string{"conformance-profile"}}, @@ -38,11 +38,11 @@ var generatedCommandContractMetadataByName = map[string]generatedCommandContract "gradual-adoption-guidance": {InputContractSHA256: "sha256:4752cbac81c864cb3e18a39facfd666a9707314233d54798c7f71e67d7f2800c", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.gradual-adoption-guidance.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:171fed4bb8d32a47fc5ec49796f5b0b55ed666feaccc2fbbfeb12da31d80ecc9", FlagChoices: map[string][]string{}, RouteTokens: []string{"gradual-adoption-guidance"}}, "help": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "", FlagChoices: map[string][]string{}, RouteTokens: []string{"help"}}, "impact": {InputContractSHA256: "sha256:41d3107414837955ee408d5ce94949a4c1a6b76f6949e6c1dc224bd06f6b09bc", InputSchemaSummary: []string{"schemaVersion=2", "root-shape-only definition proofkit.impact.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:73066e9a5ca48f21936111ffb7223900fb629875997f4e7b16d7fef9c4177972", FlagChoices: map[string][]string{}, RouteTokens: []string{"impact"}}, - "integration-apply": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:29d38f47080100f3b2c0d93ab76173dc6ba9599d13130c8b3bee960898cc33d3", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}, "--operation": []string{"install", "remove", "update"}, "--tool": []string{"claude", "codex"}}, RouteTokens: []string{"integration", "apply"}}, - "integration-check": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:5438119b425a58e777dfad3f028945ec8be1d3ff21d1fab49534d1b666767c2d", FlagChoices: map[string][]string{"--format": []string{"json", "text"}, "--tool": []string{"claude", "codex"}}, RouteTokens: []string{"integration", "check"}}, - "integration-plan": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:c5d95c7fd90d8560312f2c3dea032cbdafaa74cedfe1e845012c9dcd68d96b16", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}, "--operation": []string{"install", "remove", "update"}, "--tool": []string{"claude", "codex"}}, RouteTokens: []string{"integration", "plan"}}, - "integration-recover": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:0d368195d7989c2342b9b5c20a2ef2ac2a0c7be847d7352fe81ac7c4d7bfeeff", FlagChoices: map[string][]string{"--action": []string{"resume", "rollback"}, "--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"integration", "recover"}}, - "integration-source": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:bc2990004831538f6da693dabeebc08e3cad8acd3ff927325443265d2dec4dec", FlagChoices: map[string][]string{"--format": []string{"json", "text"}, "--tool": []string{"claude", "codex"}}, RouteTokens: []string{"integration", "source"}}, + "integration-apply": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:c4355fbcd1843f0f933c0c9de7df528e2f634cd8c49c1ba2cfa85f2073f5e961", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}, "--operation": []string{"install", "remove", "update"}, "--tool": []string{"claude", "codex"}}, RouteTokens: []string{"integration", "apply"}}, + "integration-check": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:c1604cb300b467140c37520fe08202531b84a4e589cfef435265a365c4235152", FlagChoices: map[string][]string{"--format": []string{"json", "text"}, "--tool": []string{"claude", "codex"}}, RouteTokens: []string{"integration", "check"}}, + "integration-plan": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:30c9cd0cef590ee72320fa96e60032f2c48babf5090e2dfe7aedcbf2e7d2580d", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}, "--operation": []string{"install", "remove", "update"}, "--tool": []string{"claude", "codex"}}, RouteTokens: []string{"integration", "plan"}}, + "integration-recover": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:b63cb3265b967d765c98f35e693b498deb52794fdc32ff3d434b097d7e7bf961", FlagChoices: map[string][]string{"--action": []string{"resume", "rollback"}, "--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"integration", "recover"}}, + "integration-source": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:ac38aae33d42c27586905f63ad14d686df2084d8095128294da27f48726ad4c9", FlagChoices: map[string][]string{"--format": []string{"json", "text"}, "--tool": []string{"claude", "codex"}}, RouteTokens: []string{"integration", "source"}}, "json-report-cli-adapter-source": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:6c3dd1c8507a90e055cf2c886089446d8560ff3e0d3ca9cc6360a3377d2d85da", FlagChoices: map[string][]string{}, RouteTokens: []string{"json-report-cli-adapter-source"}}, "migration-parity-admission": {InputContractSHA256: "sha256:0b36c0e68da3b857dac4b13e7b3bd523052459106133aa8c908a4352682e6c05", InputSchemaSummary: []string{"schemaVersion=1", "paritySetId", "sourceProofOwners[]", "targetProofkitRefs[]", "parityRecords[]", "nonClaims[]", "root-shape-only definition proofkit.migration-parity-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:8e0f8af2b205817f018b0fe133fe789661caa29007695e036bfcab63c1830f47", FlagChoices: map[string][]string{}, RouteTokens: []string{"migration-parity-admission"}}, "migration-plan": {InputContractSHA256: "sha256:58a62759a634101ce2ca9218184175134bbe5633328e1b23797b94c19fc9b11a", InputSchemaSummary: []string{"schemaVersion=1", "migrationId", "sourceProofOwners[]", "targetProofkitRefs[]", "parityEvidenceRefs[]", "retainedOwners[]", "retirementCandidates[]", "followUpCommands[]", "nonClaims[]", "root-shape-only definition proofkit.migration-plan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:f14f0381e9dc241357c346315b95b03ef5b23f1d1bbc3b00f111fbe1515ed3ff", FlagChoices: map[string][]string{}, RouteTokens: []string{"migration-plan"}}, @@ -50,7 +50,7 @@ var generatedCommandContractMetadataByName = map[string]generatedCommandContract "next": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:7394789ca6a1a275662109980d82d58f3586e6afb2689d0b3e54acb14d067c21", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"next"}}, "obligation-decision": {InputContractSHA256: "sha256:1dea2ed5c5066451d6d49b815cea99df2cdae2ef05d42fed16c8aeb45eb7f445", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.obligation-decision.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:96dc074f611bcc12e511bc803c548e4df623e2de869d3add29a3ea6386e04330", FlagChoices: map[string][]string{}, RouteTokens: []string{"obligation-decision"}}, "package-runtime-dependency-admission": {InputContractSHA256: "sha256:fc85887af9b8fcd899d245f0db30b2f2f68609822fc268126bf999082bb4115f", InputSchemaSummary: []string{"schemaVersion=1", "reportId", "expectedDependencySpec", "expectedLockfileIntegrity", "expectedPackageName", "expectedPackageVersion", "admissibleLocations{}", "packageResolution{}", "nonClaims[]", "root-shape-only definition proofkit.package-runtime-dependency-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:c012032e8c8212fd50bc2e85669cc610609ca2124ebc992c9e88f44a1ad2d5fc", FlagChoices: map[string][]string{}, RouteTokens: []string{"package-runtime-dependency-admission"}}, - "pilot-admission": {InputContractSHA256: "sha256:a1d9116ce619f7d705349ff4ae44c0f4399a281ebaa9e7d62ea304ac57af59ba", InputSchemaSummary: []string{"schemaVersion=2", "root-shape-only definition proofkit.pilot-admission.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:5aa8fe82acbe498c8fb240196d2a0b135ecfa61b0b873e6173cf0947d8631f58", FlagChoices: map[string][]string{}, RouteTokens: []string{"pilot-admission"}}, + "pilot-admission": {InputContractSHA256: "sha256:a1d9116ce619f7d705349ff4ae44c0f4399a281ebaa9e7d62ea304ac57af59ba", InputSchemaSummary: []string{"schemaVersion=2", "root-shape-only definition proofkit.pilot-admission.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:37c50d337b0c8efa09dfc700acca5122e0dd734d1428c412690b78cdee1dcca2", FlagChoices: map[string][]string{}, RouteTokens: []string{"pilot-admission"}}, "producer-policy-self-proof": {InputContractSHA256: "sha256:d48e18826000c8d415f3c44b6c686e1da6ed962ef7ca36c9f705de8c68d034f9", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.producer-policy-self-proof.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:e82a3989a743f8babc6069f7af82b1dd1ea62bad8dbb18d95e105b36f74e4276", FlagChoices: map[string][]string{}, RouteTokens: []string{"producer-policy-self-proof"}}, "proof-obligation-algebra": {InputContractSHA256: "sha256:4f176b6bc9bdbd0d96d65c071d66447d246665bda7a23269e7927f1d0b80b043", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.proof-obligation-algebra.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:f9ee9e56b349756c55856a2dab198e1ad85db70a468c38e3aeca73cfe2ed66f6", FlagChoices: map[string][]string{}, RouteTokens: []string{"proof-obligation-algebra"}}, "proof-receipt-admission": {InputContractSHA256: "sha256:8ba257066e276a48de661e52cabd3be194cba31a8a45e082f3b013a5c9a9120c", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.proof-receipt-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:40fd1426468aae342029e10dcc950e6a24142f9a09f0b3d4513622c7a2bff75c", FlagChoices: map[string][]string{}, RouteTokens: []string{"proof-receipt-admission"}}, @@ -68,8 +68,8 @@ var generatedCommandContractMetadataByName = map[string]generatedCommandContract "requirement-authoring-plan": {InputContractSHA256: "sha256:e43149e9aa24f9dfec832bbc5f92b6d419d3903bfde6f4b09198001e291883ee", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-authoring-plan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:0f4fb44df20eae47d4a67ee638c0692069a30fcbc85e1034d49561f2025a08dd", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-authoring-plan"}}, "requirement-bindings": {InputContractSHA256: "sha256:f4e9458a2cb69274c0c7a566eb3624c508faff8f140d1b783575c78e17a7c4d8", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-bindings.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:7821c7b23ff2c0ca83c64039c22400d90660cad73a60b9afb46829c539c61168", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-bindings"}}, "requirement-browser-server": {InputContractSHA256: "sha256:557d9f1e6919a6f40b831fb910340f85cdc0a0619d0c4895098308939a262e6e", InputSchemaSummary: []string{"workspace mode: schemaVersion=2", "workspace mode: workspaceId", "workspace mode: context=proofkit.requirement-context schemaVersion=2 with strict v1 adapter", "workspace mode: diffInput=proofkit.requirement-semantic-diff-input schemaVersion=2 (optional)", "workspace mode: graphInput=proofkit.requirement-traceability-graph-input schemaVersion=2 (optional)", "--session-mode values: browse|one-shot-question", "one-shot-question requires --view workspace --serve --open", "--session-timeout-seconds is 1..7200 and requires one-shot-question", "source|proof|coverage|spec-tree modes retain their owner input contracts", "root-shape-only definition proofkit.requirement-browser-server.input.v3.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:40a4ac0312d4fb921d572817331a73c629ed807caed789296f992f1482e0d932", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-browser-server"}}, - "requirement-context-compose": {InputContractSHA256: "sha256:0b8d5eace6247fd8fa01ad7372f0395ea6fa10e7f0aa9fe5bab2ff4ed8a69384", InputSchemaSummary: []string{"schemaVersion=1", "catalogId", "specTree.path", "requirementSources[] (non-empty)", "requirementSources[].nodeId", "requirementSources[].path", "expectedSourceDigest (optional sha256 ref)", "proofBinding.path (optional)", "coverage.path (optional)", "exact catalog paths only; no discovery", "root-shape-only definition proofkit.requirement-context-compose.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:dd08c6e3e66349019a349049345e64fca3d31459e42fe634e98e9a9ade8101f4", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-context-compose"}}, - "requirement-context-slice": {InputContractSHA256: "sha256:a97c99d2c36bdf90aa6f3f55adf3afff7d3576093ae3ed79bbde5aaccaf1249c", InputSchemaSummary: []string{"schemaVersion=1", "sliceId", "context=proofkit.requirement-context schemaVersion=2 with strict v1 adapter, or schemaVersion=3 closed captured project origin", "Project-origin v3 replay validates the exact canonical project and role/source partition; it does not reread live files or reinterpret the existing v1/v2 identities.", "query.profile=routing|specification|proof|coverage|review", "query.nodeIds[]|requirementIds[]|ownerIds[]|lifecycleStates[]", "query.maxDepth=0..512", "query.maxNodes=1..4096", "query.maxRequirements=1..16384", "root-shape-only definition proofkit.requirement-context-slice.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:f9dfb92adc6548f7e8171ad0e7261cf5d7ce3112f6ecd989acc8d1fb9ff5dd31", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-context-slice"}}, + "requirement-context-compose": {InputContractSHA256: "sha256:9bad6148b2e3ad28bde1c2147be1a2496ee321f642b869d00d1effcf679d414e", InputSchemaSummary: []string{"schemaVersion=1", "catalogId", "specTree.path", "requirementSources[] (non-empty)", "requirementSources[].nodeId", "requirementSources[].path", "expectedSourceDigest (optional sha256 ref)", "proofBinding.path (optional)", "coverage.path (optional)", "exact catalog paths only; no discovery", "root-shape-only definition proofkit.requirement-context-compose.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:93400d1e545e4e542f5de53b105f27dbbd5855cb44f65d72e3f0f437cdc76ad8", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-context-compose"}}, + "requirement-context-slice": {InputContractSHA256: "sha256:fa71734877cf19c1d6a5de0efa597c5201127e4a75df834304f8baa7ff20503b", InputSchemaSummary: []string{"schemaVersion=1", "sliceId", "context=proofkit.requirement-context schemaVersion=2 with strict v1 adapter, or schemaVersion=3 closed captured project origin", "Project-origin v3 replay validates the exact canonical project and role/source partition; it does not reread live files or reinterpret the existing v1/v2 identities.", "query.profile=routing|specification|proof|coverage|review", "query.nodeIds[]|requirementIds[]|ownerIds[]|lifecycleStates[]", "query.maxDepth=0..512", "query.maxNodes=1..4096", "query.maxRequirements=1..16384", "root-shape-only definition proofkit.requirement-context-slice.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:7cf7735b0fc78c24c3f9579345bdf71a7f9b5066ae8439bba62ccbedd72cc489", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-context-slice"}}, "requirement-coverage-input-compose": {InputContractSHA256: "sha256:3b4fd5dd444259db4de81c4a0b5800e8106e6ce61fa91fb00419ce56c4f493fa", InputSchemaSummary: []string{"schemaVersion=2", "root-shape-only definition proofkit.requirement-coverage-input-compose.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:bfc779b40b581207000080149d0876c1f90cf41080a83039af09ac71ced18682", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-coverage-input-compose"}}, "requirement-coverage-view": {InputContractSHA256: "sha256:202dfbf2b9929a9244ba067a6a57af361e8f7da245625d3d53f13d73e04f14e3", InputSchemaSummary: []string{"schemaVersion=2", "root-shape-only definition proofkit.requirement-coverage-view.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:b5784b98b903ef9bf9ddbac047592c32baeda96a83c3e426dee51d64d3846269", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-coverage-view"}}, "requirement-impact-input-compose": {InputContractSHA256: "sha256:c80c57489205004f92603fec541ce3d36dd0d9b65109dcfefb97bcfb07b90679", InputSchemaSummary: []string{"schemaVersion=2", "root-shape-only definition proofkit.requirement-impact-input-compose.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:b0b689f4d0b5bafc52c6041a2aa3583c9c8628aa0f13a0f0d7c42a610ed1a0d6", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-impact-input-compose"}}, @@ -89,7 +89,7 @@ var generatedCommandContractMetadataByName = map[string]generatedCommandContract "selective-gate-evidence": {InputContractSHA256: "sha256:8aa178ab7ca7c475c23707bc4e15fd3f9f8d57acf6f6dcf279677e7769a45586", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.selective-gate-evidence.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:723569262bb85d9674b2a78d3bcb6e9f4cab229b71e8c784ff1b804a7fcade71", FlagChoices: map[string][]string{}, RouteTokens: []string{"selective-gate-evidence"}}, "selective-gate-obligation-decision-input": {InputContractSHA256: "sha256:85761fcbc0ea94239d55bf379d0592a6ca814e6612a2d609a651f6cdaf8ca10a", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.selective-gate-obligation-decision-input.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:ab9dddabe975238d7019266c43350afa2df1a61d4c2eb7bc23afd520b588a2da", FlagChoices: map[string][]string{}, RouteTokens: []string{"selective-gate-obligation-decision-input"}}, "selective-gate-plan": {InputContractSHA256: "sha256:5293a5a4c7d8426cf637e6f8d252095ca0eb1714365bb89bec83307b778c678a", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.selective-gate-plan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:d7bffed853af5595af08b03859be01c283a3bdff1b3502d94ddc190889977647", FlagChoices: map[string][]string{}, RouteTokens: []string{"selective-gate-plan"}}, - "self-check": {InputContractSHA256: "sha256:ec19af40f151f3b316784910c08dfa415672e080f270d07979938794de25dd19", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.self-check.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:c7dc1f2eacb8077895c8d29c5f496ef053b89ae26e145acbc80b1388fb1c7020", FlagChoices: map[string][]string{}, RouteTokens: []string{"self-check"}}, + "self-check": {InputContractSHA256: "sha256:84ec78b9c24b1aa80ab263d3f9a4afd5a1d60646d99816c8595421fc9a69e37b", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.self-check.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:cd1836a5728581d0d0f7da72d23768fdfd96d3765dc5fc7cced395c6baef513b", FlagChoices: map[string][]string{}, RouteTokens: []string{"self-check"}}, "spec-overview-claims": {InputContractSHA256: "sha256:2490dcd34ba7485e13f8f33e8a288a0463c4c52cc6b0d82c57777466927e49a4", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.spec-overview-claims.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:554f3a7020e9820ccb90672629fd769c52b2f298f356040aa3b0a817666cbfbf", FlagChoices: map[string][]string{}, RouteTokens: []string{"spec-overview-claims"}}, "spec-proof-bundle-admission": {InputContractSHA256: "sha256:2dd04eb5ad2bd26758b434c2f427347efd491dab5a50b1197c8a9f98113be1b5", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.spec-proof-bundle-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:a6ac0c69d19caf97e9a8c95808b83cdb3f908720923739aa59ef646d27c90870", FlagChoices: map[string][]string{}, RouteTokens: []string{"spec-proof-bundle-admission"}}, "stack-preset": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:ef5920f363a4a96dcac308ea8412260a06e64ba4876460a369aefb8983130a9d", FlagChoices: map[string][]string{"--preset": []string{"agentic_runtime_repo", "generated_docs_contract_repo", "python_service", "python_typescript_service", "typescript_monorepo", "typescript_workspace"}}, RouteTokens: []string{"stack-preset"}}, diff --git a/internal/app/command_help.go b/internal/app/command_help.go index ecaab6ba..452285b5 100644 --- a/internal/app/command_help.go +++ b/internal/app/command_help.go @@ -12,6 +12,7 @@ import ( "github.com/research-engineering/agentic-proofkit/internal/command/proofreceiptadmission" "github.com/research-engineering/agentic-proofkit/internal/command/receiptcurrentnessscope" "github.com/research-engineering/agentic-proofkit/internal/command/requirementauthoringplan" + "github.com/research-engineering/agentic-proofkit/internal/command/requirementcontext" "github.com/research-engineering/agentic-proofkit/internal/command/requirementcoverageinput" "github.com/research-engineering/agentic-proofkit/internal/command/specproofbundleadmission" "github.com/research-engineering/agentic-proofkit/internal/kernel/cliexec" @@ -139,6 +140,9 @@ func commandUsageWithRenderer(descriptor commandDescriptor, renderer cliexec.Ren if descriptor.name == "requirement-coverage-input-compose" { lines = append(lines, "", strings.TrimSuffix(requirementcoverageinput.InputGuide(renderer), "\n")) } + if descriptor.name == "requirement-context-compose" { + lines = append(lines, "", strings.TrimSuffix(requirementcontext.InputGuide(renderer), "\n")) + } if descriptor.name == "receipt-currentness-scope" { lines = append(lines, "", strings.TrimSuffix(receiptcurrentnessscope.InputGuide(renderer), "\n")) } diff --git a/internal/app/context_input_guide_test.go b/internal/app/context_input_guide_test.go new file mode 100644 index 00000000..3a71c98c --- /dev/null +++ b/internal/app/context_input_guide_test.go @@ -0,0 +1,174 @@ +package app + +import ( + "bytes" + "crypto/sha256" + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/research-engineering/agentic-proofkit/internal/command/requirementcontext" + "github.com/research-engineering/agentic-proofkit/internal/kernel/cliexec" +) + +func TestContextInputGuideIsLazyAndCarrierBound(t *testing.T) { + _, help := receiptHelpTemplate(t, "requirement-context-compose") + if len(help) > 10<<10 { + t.Fatal("context guide exceeds its bounded help surface") + } + for _, carrier := range []struct{ profile, python string }{ + {cliexec.ProfilePath, ""}, {cliexec.ProfileNPMOffline, ""}, {cliexec.ProfilePythonModule, "/example/python 3"}, + } { + renderer, err := cliexec.AdmitLauncherProfile(carrier.profile, carrier.python) + if err != nil { + t.Fatal(err) + } + descriptor, _ := commandDescriptorFor("requirement-context-compose") + want := [][]string{ + {"requirement-spec-tree", "--input", "", "--input-pointer", "/tree"}, + {"requirement-context-compose", "--input", "", "--input-pointer", "/catalog", "--repo-root", ""}, + } + if got := guideCommands(t, commandUsageWithRenderer(descriptor, renderer), "Requirement context input guide:", renderer); !reflect.DeepEqual(got, want) { + t.Fatalf("context guide commands differ: %v", got) + } + } + for _, args := range [][]string{{"help"}, {"help", "families"}, {"native-evidence-guidance"}, {"changed-path-set", "--help"}, {"requirement-spec-tree", "--help"}} { + code, output, diagnostic := executeAgentWorkflowCLI(t, args, panicReader{}, PresentationCapabilities{}) + if code != 0 || diagnostic != "" || strings.Contains(output, "Requirement context input guide:") { + t.Fatal("context guide is not demand-loaded") + } + } +} + +func TestContextGuideComposesActualChildInputs(t *testing.T) { + packet, help := receiptHelpTemplate(t, "requirement-context-compose") + root := t.TempDir() + materialize := adoptionHelpPacket(t, root, "fresh") + source := materialize["requirementSources"].([]any)[0].(map[string]any) + binding := materialize["requirementProofBinding"].(map[string]any) + bindingRecord := binding["record"].(map[string]any) + writeCLIJSONFixture(t, root, source["requirementsPath"].(string), source) + writeCLIJSONFixture(t, root, binding["path"].(string), binding["record"]) + catalog := packet["catalog"].(map[string]any) + treePath := catalog["specTree"].(map[string]any)["path"].(string) + commands := guideCommands(t, help, "Requirement context input guide:", cliexec.PathRenderer()) + operands := map[string]string{"": "-", "": root} + admitted := runAdoptionHelpCLI(t, adoptionHelpJSON(t, packet), fillGuideOperands(t, commands[0], operands)...) + if admitted["state"] != "passed" { + t.Fatal("tree example did not pass its owner") + } + writeCLIJSONFixture(t, root, treePath, packet["tree"]) + args := fillGuideOperands(t, commands[1], operands) + composed := runAdoptionHelpCLI(t, adoptionHelpJSON(t, packet), args...) + if _, err := requirementcontext.AdmitSnapshot(composed); err != nil { + t.Fatal(err) + } + if composed["expectedDigestCoverage"] != "none" || composed["state"] != nil { + t.Fatal("context identity or expected-digest boundary differs") + } + projections := composed["projections"].(map[string]any) + if !equalCLIJSON(t, projections["requirementSources"].([]any)[0], source) || !equalCLIJSON(t, projections["specTree"], packet["tree"]) { + t.Fatal("composition lost source or tree input semantics") + } + if !equalCLIJSON(t, projections["proofBinding"], bindingRecord) { + t.Fatal("composition lost the requested binding input") + } + bindingBytes, err := os.ReadFile(filepath.Join(root, binding["path"].(string))) + if err != nil { + t.Fatal(err) + } + wantBindingSource := map[string]any{ + "kind": "proof_binding", "path": binding["path"], "sourceRef": "proof_binding:example.bindings", + "currentDigest": fmt.Sprintf("sha256:%x", sha256.Sum256(bindingBytes)), + } + bindingSources := []any{} + for _, raw := range composed["sources"].([]any) { + if raw.(map[string]any)["kind"] == "proof_binding" { + bindingSources = append(bindingSources, raw) + } + } + if !equalCLIJSON(t, bindingSources, []any{wantBindingSource}) { + t.Fatal("binding provenance does not match actual file bytes") + } + sourceEntry := catalog["requirementSources"].([]any)[0].(map[string]any) + sourceBytes, err := os.ReadFile(filepath.Join(root, sourceEntry["path"].(string))) + if err != nil { + t.Fatal(err) + } + sourceEntry["expectedSourceDigest"] = fmt.Sprintf("sha256:%x", sha256.Sum256(sourceBytes)) + if result := runAdoptionHelpCLI(t, adoptionHelpJSON(t, packet), args...); result["expectedDigestCoverage"] != "partial" { + t.Fatal("actual expected bytes were not admitted") + } + for _, mutation := range []string{"node", "source", "digest", "report-instead-of-tree", "wrapper-instead-of-catalog"} { + t.Run(mutation, func(t *testing.T) { + changed := decodeCLIJSON(t, string(adoptionHelpJSON(t, packet))).(map[string]any) + entry := changed["catalog"].(map[string]any)["requirementSources"].([]any)[0].(map[string]any) + callArgs := append([]string{}, args...) + wantDiagnostic := "" + switch mutation { + case "node": + entry["nodeId"] = "example.other" + wantDiagnostic = "source node does not match the specification tree" + case "source": + wrong := decodeCLIJSON(t, string(adoptionHelpJSON(t, source))).(map[string]any) + wrong["sourceId"] = "example.other" + writeCLIJSONFixture(t, root, source["requirementsPath"].(string), wrong) + delete(entry, "expectedSourceDigest") + if report := runAdoptionHelpCLI(t, adoptionHelpJSON(t, wrong), "requirement-source-admission", "--input", "-"); report["state"] != "passed" { + t.Fatal("source counterexample is not independently owner-valid") + } + wantDiagnostic = "source is not referenced by the specification tree" + t.Cleanup(func() { writeCLIJSONFixture(t, root, source["requirementsPath"].(string), source) }) + case "digest": + entry["expectedSourceDigest"] = "sha256:" + strings.Repeat("0", 64) + wantDiagnostic = "expected digest mismatch" + case "report-instead-of-tree": + writeCLIJSONFixture(t, root, treePath, admitted) + wantDiagnostic = "admit specification tree" + t.Cleanup(func() { writeCLIJSONFixture(t, root, treePath, packet["tree"]) }) + case "wrapper-instead-of-catalog": + callArgs = []string{"requirement-context-compose", "--input", "-", "--repo-root", root} + wantDiagnostic = "requirement context catalog" + } + code, output, diagnostic := executeAgentWorkflowCLI(t, callArgs, bytes.NewReader(adoptionHelpJSON(t, changed)), PresentationCapabilities{}) + if code != 1 || output != "" || !strings.Contains(diagnostic, wantDiagnostic) { + t.Fatalf("invalid %s accepted: %d %q %q", mutation, code, output, diagnostic) + } + }) + } + t.Run("known-node-wrong-relation", func(t *testing.T) { + changed := decodeCLIJSON(t, string(adoptionHelpJSON(t, packet))).(map[string]any) + tree := changed["tree"].(map[string]any) + tree["nodes"] = append(tree["nodes"].([]any), map[string]any{ + "nodeId": "example.other", "nodeKind": "module_spec", "label": "Other scope", + "displayOrder": json.Number("2"), "callerAnnotations": []any{}, + "sourceRefs": []any{map[string]any{ + "sourceRefId": "example.other.overview", "sourceRefKind": "source_id", + "sourceRole": "overview", "sourceId": "example.requirements", + }}, + }) + tree["edges"] = []any{map[string]any{"parentNodeId": "example.root", "childNodeId": "example.other"}} + if report := runAdoptionHelpCLI(t, adoptionHelpJSON(t, tree), "requirement-spec-tree", "--input", "-"); report["state"] != "passed" { + t.Fatal("relation counterexample tree must be owner-valid") + } + writeCLIJSONFixture(t, root, treePath, tree) + runAdoptionHelpCLI(t, adoptionHelpJSON(t, changed), args...) + changed["catalog"].(map[string]any)["requirementSources"].([]any)[0].(map[string]any)["nodeId"] = "example.other" + code, output, diagnostic := executeAgentWorkflowCLI(t, args, bytes.NewReader(adoptionHelpJSON(t, changed)), PresentationCapabilities{}) + if code != 1 || output != "" || !strings.Contains(diagnostic, "source node does not match the specification tree") { + t.Fatalf("known-but-unrelated node accepted: %d %q %q", code, output, diagnostic) + } + }) + t.Run("optional-binding-absent", func(t *testing.T) { + changed := decodeCLIJSON(t, string(adoptionHelpJSON(t, packet))).(map[string]any) + delete(changed["catalog"].(map[string]any), "proofBinding") + value := runAdoptionHelpCLI(t, adoptionHelpJSON(t, changed), args...) + if _, present := value["projections"].(map[string]any)["proofBinding"]; present { + t.Fatal("absent optional binding became a fabricated projection") + } + }) +} diff --git a/internal/command/changeworkflowplan/input_guide.go b/internal/command/changeworkflowplan/input_guide.go index 7803801f..f7c89994 100644 --- a/internal/command/changeworkflowplan/input_guide.go +++ b/internal/command/changeworkflowplan/input_guide.go @@ -16,8 +16,20 @@ const inputGuide = `Current-subject review input guide: artifactPath, discover dependencies, authenticate a reviewer or approve a change. A supplied digest is not proof that current files still have that content. + For canonical source/binding records and explicit observed-file provenance: + {{cli}} requirement-context-compose --help + That snapshot still excludes undeclared native dependencies and is not a + complete review subject by itself. + Consumer adapter steps (implement in the repository's change/check workflow): - 1. Read exact base/current inputs from an explicit bounded scope. Bind source + 1. Declare which plane is being approved: working tree, Git index, immutable + commit or materialized artifact. Read that exact plane; a working-tree + check alone cannot approve different staged bytes. If partial staging is + unsupported, require exact index/worktree bytes and executable modes for + the complete input scope, including untracked inputs and filter effects. + Otherwise materialize and check the selected index/commit separately. + Proofkit performs neither Git inspection nor this consumer precondition. + Read exact base/current inputs from an explicit bounded scope. Bind source namespace, requirement/scenario meaning, matched native witness/path/selector, assertion and helper inputs, command argv, environment/toolchain and receipt policy. Enumerate transitive semantic dependencies; omitted inputs cannot diff --git a/internal/command/requirementcontext/input_guide.go b/internal/command/requirementcontext/input_guide.go new file mode 100644 index 00000000..d44255c4 --- /dev/null +++ b/internal/command/requirementcontext/input_guide.go @@ -0,0 +1,88 @@ +package requirementcontext + +import ( + "strings" + + "github.com/research-engineering/agentic-proofkit/internal/command/requirementspectree" + "github.com/research-engineering/agentic-proofkit/internal/kernel/cliexec" + "github.com/research-engineering/agentic-proofkit/internal/kernel/stablejson" +) + +// InputGuide delegates the child tree representation to its existing owner. +func InputGuide(renderer cliexec.Renderer) string { + tree := requirementspectree.Tree{ + TreeID: "example.tree", RootNodeID: "example.root", + Nodes: []requirementspectree.Node{{ + NodeID: "example.root", NodeKind: "meta_spec", Label: "Request behavior", DisplayOrder: 1, + SourceRefs: []requirementspectree.SourceRef{{ + SourceRefID: "example.root.requirements", SourceRefKind: "source_id", + SourceRole: "requirements", SourceID: "example.requirements", + }}, + }}, + } + packet := map[string]any{ + "tree": requirementspectree.TreeValue(tree), + "catalog": map[string]any{ + "schemaVersion": 1, "catalogId": "example.context", + "specTree": map[string]any{"path": "proofkit/spec-tree.json"}, + "requirementSources": []any{map[string]any{ + "path": "docs/specs/requests/requirements.v1.json", "nodeId": "example.root", + }}, + "proofBinding": map[string]any{"path": "proofkit/requirement-bindings.json"}, + }, + } + encoded, err := stablejson.Marshal(packet) + if err != nil { + panic("invalid static context guide template: " + err.Error()) + } + return strings.NewReplacer("{{cli}}", renderer.DisplayCommand(), "{{packet}}", strings.TrimSuffix(string(encoded), "\n")).Replace(inputGuide) +} + +const inputGuide = `Requirement context input guide: + Compose a derived snapshot from explicitly named files under --repo-root. + This command reads those files; it does not scan for specifications, write + sources, approve meaning, execute witnesses or authenticate caller digests. + + First obtain connected source and binding inputs from: + {{cli}} adopt materialize plan --help + Its /requirementSources/0 has sourceId example.requirements and its binding + is /requirementProofBinding/record. Review/adapt those complete inputs and + persist them through the materialization recipe or an authorized repository + writer. The paths below match that example; no admission report is a source. + + The tree below is a separate routing input, not a requirement source. Tree + schemaVersion 2 does not mean requirement-source v2. Match each catalog nodeId + to a tree node whose requirements-role sourceRef names that source's sourceId. + Source IDs and global requirement IDs must remain unique within the context. + Additional sources need their own catalog entries and matching tree references. + +Connected context packet (store in a caller-selected scratch file): +` + "```json\n" + `{{packet}} +` + "```\n" + ` + Validate /tree, then persist that SAME tree input at proofkit/spec-tree.json + under the selected root using an authorized repository writer. Do not store + the requirement-spec-tree report in its place. This command does not create it. + The packet's /catalog is the compose input; the outer packet is not the catalog. + + {{cli}} requirement-spec-tree --input --input-pointer /tree + {{cli}} requirement-context-compose --input --input-pointer /catalog --repo-root + + Require exit0 and state passed from tree admission. Compose exit0 emits a + context, not a report with state passed. Retain its complete stdout bytes: + projections contain child-owned canonical records; sources retain observed + paths/digests; snapshotId binds this captured context. It is not proof that + the files stayed unchanged, the witnesses ran or the producer is trusted. + With no expectedSourceDigest operands, expectedDigestCoverage is none. + For a previously captured expectation, put expectedSourceDigest on the + relevant catalog entry as sha256:<64 lowercase hex digits>, computed from + the exact retained file bytes. A mismatch rejects; computing an expectation + from current bytes alone does not establish historical freshness or trust. + + proofBinding may be omitted when no binding is available. coverage may be + added as {"path":""} after preparing an actual + requirement-coverage-view INPUT, not its rendered report; see: + {{cli}} requirement-coverage-input-compose --help + Do not invent an empty passing coverage report. Continue with + requirement-context-slice, requirement-semantic-diff or + requirement-traceability-graph using their command-specific --help. +` diff --git a/internal/command/stackpreset/preset_ids_generated.go b/internal/command/stackpreset/preset_ids_generated.go index 181c6f0b..2dbbb5a2 100644 --- a/internal/command/stackpreset/preset_ids_generated.go +++ b/internal/command/stackpreset/preset_ids_generated.go @@ -1,6 +1,6 @@ // Code generated by internal/tools/commandcontractgen; DO NOT EDIT. package stackpreset -const presetContractSourceSHA256 = "7bb0e36255f4b08eca5f067f133b16b5a35111789ce1a46eccf63adccf2cf72c" +const presetContractSourceSHA256 = "7a4a0bf93a1aca3dd91d84e7c0e6303944d301a665a3649293639c3c85d01fb1" var presetIDs = []string{"agentic_runtime_repo", "generated_docs_contract_repo", "python_service", "python_typescript_service", "typescript_monorepo", "typescript_workspace"} diff --git a/internal/tools/releasechange/record_test.go b/internal/tools/releasechange/record_test.go index 7a262026..26499f41 100644 --- a/internal/tools/releasechange/record_test.go +++ b/internal/tools/releasechange/record_test.go @@ -197,7 +197,7 @@ func TestCurrentChangeRecordNamesReviewedSemanticChanges(t *testing.T) { var currentBreakingChanges = []Change{} var currentAdditions = []Change{ - {ChangeID: "proofkit.source.compatibility-boundaries", Summary: "Make common-field source correspondence, public v1 identity and paths, qualified binding projection and private-model resource-domain boundaries permanent cross-owner regression checks. Clarify that the internal grouped codec is not a public source transition. Runtime admission, CLI contracts, source formats, resource limits, dependencies and supported platforms remain unchanged."}, + {ChangeID: "proofkit.traceability.input-guidance", Summary: "Add lazy CLI recipes for source-intake roles, canonical catalog/tree composition and current-subject review of the exact publication plane. Strengthen connected input and normative-guidance regression oracles and explain scenario-storage ownership. Public source formats, command input/output semantics, runtime admission, native execution authority, dependencies and supported platforms remain unchanged."}, } var currentMigrationSteps = []string{} @@ -220,7 +220,7 @@ func validateCurrentChangeRecord(record Record, notes string) error { func currentExpectedReleaseNotes() string { lines := []string{ - "# @research-engineering/agentic-proofkit 0.14.18", + "# @research-engineering/agentic-proofkit 0.14.19", "", "## Breaking Contract Changes", "", @@ -273,7 +273,7 @@ func currentExpectedReleaseNotes() string { "Primary npm channel:", "", "```bash", - "npm install --save-dev --save-exact @research-engineering/agentic-proofkit@0.14.18", + "npm install --save-dev --save-exact @research-engineering/agentic-proofkit@0.14.19", "```", "", "Pre-1.0 npm consumers must keep this dependency exact-pinned.", @@ -285,7 +285,7 @@ func currentExpectedReleaseNotes() string { "## Rollback", "", "- First follow the migration and persistent-state compatibility restrictions above; changing a package pin does not roll back repository state.", - "- Pin npm consumers to the previous admitted version 0.14.17 with `npm install --save-dev --save-exact @research-engineering/agentic-proofkit@0.14.17`.", + "- Pin npm consumers to the previous admitted version 0.14.18 with `npm install --save-dev --save-exact @research-engineering/agentic-proofkit@0.14.18`.", "- Treat local package artifacts as candidates until registry identity is proven.", ) return strings.Join(lines, "\n") + "\n" diff --git a/package-lock.json b/package-lock.json index 280f2b10..5cdaf602 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@research-engineering/agentic-proofkit", - "version": "0.14.18", + "version": "0.14.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@research-engineering/agentic-proofkit", - "version": "0.14.18", + "version": "0.14.19", "cpu": [ "arm64", "x64" diff --git a/package.json b/package.json index a82cd27a..8a708e11 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@research-engineering/agentic-proofkit", "description": "Reusable proof profile, report, graph, and witness-planning primitives.", - "version": "0.14.18", + "version": "0.14.19", "type": "module", "license": "MIT", "sideEffects": false, diff --git a/proofkit/cli-contract.v2.json b/proofkit/cli-contract.v2.json index 22ab696e..6dac6a36 100644 --- a/proofkit/cli-contract.v2.json +++ b/proofkit/cli-contract.v2.json @@ -143,7 +143,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, { @@ -282,7 +282,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, { @@ -396,7 +396,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, { @@ -1187,7 +1187,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, { @@ -1471,7 +1471,7 @@ "rootDefinitionDigest": "sha256:6632a481380bfb07d754ef622661b291f805b1b168b1824089bdd5cc9bdd4b0e", "nativeSource": { "path": "internal/command/changeworkflowplan", - "canonicalDigest": "sha256:588e5f2240b1ac4f29bd95d808747e697d809022ee8e5c61cd416dcc680e839d", + "canonicalDigest": "sha256:31dce71e6b1c00f6a9973684cae27d12c6b473b37f1312bc392af67ae9f298ba", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -1506,7 +1506,7 @@ "rootDefinitionDigest": "sha256:ca7b2acffdcbfd28222eb61f2bd8687f76f20cb789b712ee3ffc2756a3674ff8", "nativeSource": { "path": "internal/command/changeworkflowplan", - "canonicalDigest": "sha256:588e5f2240b1ac4f29bd95d808747e697d809022ee8e5c61cd416dcc680e839d", + "canonicalDigest": "sha256:31dce71e6b1c00f6a9973684cae27d12c6b473b37f1312bc392af67ae9f298ba", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { @@ -2506,7 +2506,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, { @@ -2612,7 +2612,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, { @@ -2740,7 +2740,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, { @@ -2867,7 +2867,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, { @@ -2965,7 +2965,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, { @@ -3640,7 +3640,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, { @@ -5023,7 +5023,7 @@ "rootDefinitionDigest": "sha256:41bc233c96bd468bc96eeb0447e037cacdbcf92bb04161052303e8747e4282f2", "nativeSource": { "path": "internal/command/requirementcontext", - "canonicalDigest": "sha256:2ee9c04ca2b3775a8e1d1f066a3eefcee71ea4f3e24c6b6690fd2e3c4c2aeb40", + "canonicalDigest": "sha256:733471270d82a1a05b5ccbd8444b17a26aa6d5095b3619399e098a0dc515f620", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -5063,7 +5063,7 @@ "rootDefinitionDigest": "sha256:cdadd7b589d682a122cfe2b801f001a3b22a2269aa4977ebe073932d11e1f816", "nativeSource": { "path": "internal/command/requirementcontext", - "canonicalDigest": "sha256:2ee9c04ca2b3775a8e1d1f066a3eefcee71ea4f3e24c6b6690fd2e3c4c2aeb40", + "canonicalDigest": "sha256:733471270d82a1a05b5ccbd8444b17a26aa6d5095b3619399e098a0dc515f620", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { @@ -5110,7 +5110,7 @@ "rootDefinitionDigest": "sha256:43852cf1a52b3c1f000ec95e460fbad157a6b6a39958d20f81c9e1e4d31e3c6f", "nativeSource": { "path": "internal/command/requirementcontext", - "canonicalDigest": "sha256:2ee9c04ca2b3775a8e1d1f066a3eefcee71ea4f3e24c6b6690fd2e3c4c2aeb40", + "canonicalDigest": "sha256:733471270d82a1a05b5ccbd8444b17a26aa6d5095b3619399e098a0dc515f620", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -5150,7 +5150,7 @@ "rootDefinitionDigest": "sha256:61f7fd43e2e9da5bde9b61bb86e02cbf337a43c6891ca888c1d42936e5b16456", "nativeSource": { "path": "internal/command/requirementcontext", - "canonicalDigest": "sha256:2ee9c04ca2b3775a8e1d1f066a3eefcee71ea4f3e24c6b6690fd2e3c4c2aeb40", + "canonicalDigest": "sha256:733471270d82a1a05b5ccbd8444b17a26aa6d5095b3619399e098a0dc515f620", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { @@ -6935,7 +6935,7 @@ "rootDefinitionDigest": "sha256:3c842174dff5361e7f83166469b832805e05aa314b073c16234b5b64e346281e", "nativeSource": { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -6964,7 +6964,7 @@ "rootDefinitionDigest": "sha256:0ea95e277ebe44cd2de42c29b47c38686ac0b6b390d8965367437b3fe138e209", "nativeSource": { "path": "internal/app", - "canonicalDigest": "sha256:1049e6b312d9935bda1e536852051d28dc2b651215fbec2469e6b4c70e685e28", + "canonicalDigest": "sha256:9e5523dd43503f069a0facd44e7a7d85599c37accbcaf739d46e3cdf2646a601", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { diff --git a/release/change-record.v2.json b/release/change-record.v2.json index 4e65e13d..e49ab613 100644 --- a/release/change-record.v2.json +++ b/release/change-record.v2.json @@ -1,13 +1,13 @@ { "schemaVersion": 2, - "previousVersion": "0.14.17", - "version": "0.14.18", + "previousVersion": "0.14.18", + "version": "0.14.19", "changeClass": "compatible", "breakingChanges": [], "additions": [ { - "changeId": "proofkit.source.compatibility-boundaries", - "summary": "Make common-field source correspondence, public v1 identity and paths, qualified binding projection and private-model resource-domain boundaries permanent cross-owner regression checks. Clarify that the internal grouped codec is not a public source transition. Runtime admission, CLI contracts, source formats, resource limits, dependencies and supported platforms remain unchanged." + "changeId": "proofkit.traceability.input-guidance", + "summary": "Add lazy CLI recipes for source-intake roles, canonical catalog/tree composition and current-subject review of the exact publication plane. Strengthen connected input and normative-guidance regression oracles and explain scenario-storage ownership. Public source formats, command input/output semantics, runtime admission, native execution authority, dependencies and supported platforms remain unchanged." } ], "migration": { From 20b2a741335f96cf0fe6c55896485c64fded7779 Mon Sep 17 00:00:00 2001 From: iperev Date: Sun, 20 Sep 2026 16:43:13 +0200 Subject: [PATCH 2/2] docs: close portable traceability design work --- BACKLOG.md | 65 ------------------------------------------------------ 1 file changed, 65 deletions(-) diff --git a/BACKLOG.md b/BACKLOG.md index 5163bddb..9a5445ed 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -56,68 +56,3 @@ records, generated release manifests, or the owning docs named above. | BLOCKED | RELOCATION-01 | Add provenance-bounded witness relocation candidates without introducing a second binding path or trusting a caller-authored prior digest; detailed candidate contract is retained in [issue #66](https://github.com/research-engineering/agentic-proofkit/issues/66). | An owner-admitted content-addressed baseline binds witness id, prior path and digest, source revision, evidence class, authentication non-claims, and freshness non-claims; the scanner then proves the zero/one/many match partition while remaining non-current until fresh execution evidence exists. | | BLOCKED | RELEASE-01 | Prove signed protected-tag release policy as provider-side release governance, not source-only intent. | Repository tag protection/ruleset and release workflow variables require signed annotated release tags; the next public release records provider-side evidence or the row is explicitly retired as an accepted non-claim. | | DEFERRED | WEB-PUBLISH-DESIGN-01 | Investigate optional publication of the specification browser at a configurable domain with authentication. Preserve local loopback serving and local browser opening as the default workflow; remote publication must be explicit and opt-in. | After the current program, compare static export with external hosting, a bounded deployment adapter, and an authenticated hosted server. Decide whether any capability belongs in Proofkit or should remain external, using a concrete consumer need and maintenance/security costs. The decision must define URL and authentication configuration, hosting/TLS/access-control ownership, source-disclosure and secret boundaries, content freshness, and preservation of derived-view authority. Require a feasibility witness and negative cases for unauthorized access and unintended publication before accepting an implementation plan; otherwise retain local-only behavior and retire the candidate with rationale. This row authorizes investigation, not exposure of the current server or deployment. | -| NEXT | TRACEABILITY-DESIGN-01 | Complete the specification, scenario, native-test and execution-evidence workflow, including source intake, change impact and explanatory diagrams; see the bounded questions below. Reuse current public contracts and retained evidence; lazy input guidance alone does not close the complete workflow. | An owner-reviewed design and implementation decision resolves every question below against current Proofkit, StrictDoc and OpenSpec capabilities; an executable example and adversarial controls justify the selected ownership, storage and invalidation model. Existing mechanisms are reused when sufficient; unsupported additions are explicitly rejected rather than assumed necessary. | - -## TRACEABILITY-DESIGN-01 - -This is active design and validation work, not a claim that the following capabilities are -implemented or absent. First establish the current behavior and reuse existing -owners before proposing a new command, record, parser or workflow engine. - -### Questions And Acceptance Evidence - -- **Source intake.** Evaluate requirements derived from existing code, an - external specification, design and implementation-plan documents, tests and - test-coverage observations, or explicit product intent. Preserve provenance, - assumptions and unresolved contradictions. Distinguish an explicit - code-as-baseline mode from an audit-from-code mode. A bounded, explicitly - selected code scan and assisted specification authoring must not be confused - with the current recognized-file scan or with automatic owner acceptance. -- **Normative authority.** Keep one editable owner of each behavioral promise. - Generated candidates require owner review before becoming specifications; - tests and observed implementation behavior cannot silently redefine them. -- **Complete chain.** Evaluate stable requirement ID -> scenario ID -> native - witness/path/selector -> command/environment -> actual result -> coverage. - Check both directions, many-to-many relationships, discovered-but-unmapped - tests, dangling references, parametrized tests, explicit exclusions and - missing, skipped or stale execution. A link is not an adequate oracle. -- **Change propagation.** For a semantic change anywhere in the chain, derive - the affected dependency closure and automatically require confirmation or - update of affected specifications, scenarios, tests, bindings and evidence. - Changes to tests must trigger upstream impact review, not automatic rewriting - of the specification. Distinguish semantic from presentation-only changes; - preserve unaffected approvals and bind confirmations to exact current - subjects. Exercise additions, removals, renames, changed assertions, - environment changes and stale confirmations without forcing cosmetic edits. -- **Scenario storage decision.** Compare separate scenario documents, scenarios - in the specification, structured declarations in test files, and test-adjacent - comments or annotations parsed into a derived inventory. Justify whether an - intermediate scenario document has independent meaning worth maintaining. - Preserve stable IDs, machine readability, native discovery, browser rendering, - cross-language portability and one editable source. Measure authoring, token, - review, drift, parser and maintenance cost rather than selecting by aesthetics. -- **Agent guidance and cookbook.** Check that the agent receives actionable, - bounded instructions and a template for repository-specific discovery, - bindings, executable checks and evidence admission. Add or repair a complete - cookbook example only where the current documentation is insufficient. Show - a positive control, a behavior-breaking near miss, an unmapped or missing - test, and stale evidence; do not fabricate test completeness. -- **Diagrams and explanation.** Audit README, adoption guidance, reference and - cookbook coverage for two understandable visual routes: multiple candidate - sources -> invariant extraction -> owner review -> canonical specification; - and specification -> scenario -> native test -> command/environment -> result - and coverage. Explain reverse impact review separately from forward proof - flow. Link each explanation to its current contract owner and validate actual - rendering; diagram count alone is not a completeness metric. -- **Migration and alternatives.** Compare the same workflow with StrictDoc, - including its DSL and test-report integration, and OpenSpec. Run a bounded - example in which the same externally observable scenarios exercise both a - TypeScript implementation and a Python/FastAPI replacement. Keep requirement - and scenario IDs while allowing native adapters to differ. Passing selected - scenarios does not prove equivalence for every possible behavior. -- **Decision closure.** Document why the chosen structure is preferable to the - strongest lower-cost alternative, including retaining the current structure - when sufficient. Keep the durable rationale in the narrowest appropriate - explanation or contract owner. Implement only admitted gaps, with negative - whole-chain tests, honest non-claims and measured cost; do not create a second - normative scenario store or an unjustified universal test engine.