diff --git a/BACKLOG.md b/BACKLOG.md index 9ac1e64..955ea13 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -57,3 +57,4 @@ records, generated release manifests, or the owning docs named above. | 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 | INSTALLED-CONSUMER-01 | Evaluate one carrier-neutral installed-contract and route-verification protocol without merging npm process transport with Python module transport. | Exact npm and wheel decision tables plus a shared mutant corpus first prove behavioral equivalence for contract admission, route/help identity, and byte-bound carrier checks; extract only the proven common protocol while retaining carrier-specific installation and execution owners, or retire the row if the common layer does not reduce semantic duplication. | | DEFERRED | CLI-ARGS-01 | Evaluate one immutable typed parse result between descriptor admission and command execution instead of independently interpreting already-admitted command operands. | A reproducible descriptor-versus-handler drift falsifier establishes the defect class; a bounded prototype proves exact flag, multiplicity, value, help, input, and presentation parity across every affected command with no new ambient authority or generic option bag; otherwise retain the current bounded parsers and retire the row. | +| 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. | diff --git a/internal/app/adoption_input_guide_contract_test.go b/internal/app/adoption_input_guide_contract_test.go new file mode 100644 index 0000000..ad4da84 --- /dev/null +++ b/internal/app/adoption_input_guide_contract_test.go @@ -0,0 +1,81 @@ +package app + +import ( + "fmt" + "slices" + "testing" +) + +const inventoryInputGuideVersionSummary = "aggregate input contract v2; direct inventory schemaVersion=1; proof-binding-derived projection schemaVersion=2; discovery-draft projection schemaVersion=1" + +// Only the admitted explanatory correction is normalized for historical ABI +// comparison. Every other field remains subject to the predecessor fingerprint. +func normalizeInventoryInputGuideContract(input map[string]any) (map[string]any, error) { + summary, ok := input["compatibilitySummary"].([]any) + if !ok || len(summary) == 0 || summary[0] != inventoryInputGuideVersionSummary { + return nil, fmt.Errorf("inventory input version summary differs from its declared correction") + } + input = clonePublicABIRecord(input) + summary = slices.Clone(summary) + summary[0] = "schemaVersion=2" + input["compatibilitySummary"] = summary + return input, nil +} + +func normalizeInventoryInputGuidePublicABIDelta(current map[string]any) error { + commands, _, err := indexPublicABIRecords(current["commands"], "command") + if err != nil { + return err + } + command, ok := commands["test-evidence-inventory"] + if !ok { + return fmt.Errorf("inventory command is missing") + } + input, ok := command["inputContract"].(map[string]any) + if !ok { + return fmt.Errorf("inventory input contract is missing") + } + input, err = normalizeInventoryInputGuideContract(input) + if err != nil { + return err + } + command = clonePublicABIRecord(command) + command["inputContract"] = input + values := slices.Clone(current["commands"].([]any)) + for index, raw := range values { + if raw.(map[string]any)["command"] == "test-evidence-inventory" { + values[index] = command + } + } + current["commands"] = values + return nil +} + +func TestAdoptionInputGuideContractRejectsUndeclaredDelta(t *testing.T) { + for _, mutation := range []string{"old-summary", "wrong-version", "missing-summary", "extra-summary", "other-field"} { + t.Run(mutation, func(t *testing.T) { + current := readCLIContractRaw(t) + mutatePublicABIRecord(t, current, "commands", "command", "test-evidence-inventory", func(record map[string]any) { + input := clonePublicABIRecord(record["inputContract"].(map[string]any)) + summary := slices.Clone(input["compatibilitySummary"].([]any)) + switch mutation { + case "old-summary": + summary[0] = "schemaVersion=2" + case "wrong-version": + summary[0] = "aggregate input contract v2; direct inventory schemaVersion=2" + case "missing-summary": + summary = summary[1:] + case "extra-summary": + summary = append(summary, "Undeclared semantics.") + case "other-field": + input["contractId"] = "proofkit.undeclared.input.v1" + } + input["compatibilitySummary"] = summary + record["inputContract"] = input + }) + if verifyManagedIntegrationPublicABIDiff(readFrozenManagedIntegrationPredecessor(t), current) == nil { + t.Fatal("inventory correction hid an undeclared contract change") + } + }) + } +} diff --git a/internal/app/adoption_input_guide_test.go b/internal/app/adoption_input_guide_test.go new file mode 100644 index 0000000..2b5c15a --- /dev/null +++ b/internal/app/adoption_input_guide_test.go @@ -0,0 +1,304 @@ +package app + +import ( + "bytes" + "encoding/json" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/research-engineering/agentic-proofkit/internal/command/adoptionmaterialization" + "github.com/research-engineering/agentic-proofkit/internal/kernel/cliexec" +) + +func TestAdoptionInputGuideCLI(t *testing.T) { + var canonical string + for _, args := range [][]string{ + {"adopt", "materialize", "plan", "--help"}, + {"adopt", "materialize", "plan", "-h"}, + {"help", "adopt", "materialize", "plan"}, + } { + status, help, stderr := executeAgentWorkflowCLI(t, args, panicReader{}, PresentationCapabilities{}) + if status != 0 || stderr != "" || strings.Count(help, adoptionmaterialization.InputGuide(cliexec.PathRenderer())) != 1 { + t.Fatalf("help status=%d stderr=%q", status, stderr) + } + if canonical != "" && canonical != help { + t.Fatal("help aliases disagree") + } + canonical = help + if len(help) > 16<<10 || strings.Contains(help, "\x1b[") { + t.Fatal("on-demand guide is not bounded plain text") + } + } + for _, args := range [][]string{{"help"}, {"help", "families"}, {"changed-path-set", "--help"}, {"adopt", "materialize", "apply", "--help"}} { + status, help, stderr := executeAgentWorkflowCLI(t, args, panicReader{}, PresentationCapabilities{}) + if status != 0 || stderr != "" || strings.Contains(help, "Connected request template") { + t.Fatalf("guide must remain lazy for %v: status=%d stderr=%q", args, status, stderr) + } + } + + for command, pointer := range map[string]string{ + "requirement-source-admission": "/requirementSources/0", + "requirement-bindings": "/requirementProofBinding/record", + "test-evidence-inventory": "/testEvidenceInventory/record", + } { + t.Run(command, func(t *testing.T) { + status, help, stderr := executeAgentWorkflowCLI(t, []string{command, "--help"}, panicReader{}, PresentationCapabilities{}) + if status != 0 || stderr != "" || !strings.Contains(help, "adopt materialize plan --help") || !strings.Contains(help, pointer) { + t.Fatalf("missing CLI continuation: status=%d stderr=%q help=%s", status, stderr, help) + } + if strings.Contains(help, "Connected request template") || strings.Contains(help, "Continue with the installed README") { + t.Fatal("child help duplicated the template or requires external documentation") + } + }) + } + + 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, ok := commandDescriptorFor("requirement-source-admission") + if !ok { + t.Fatal("requirement source descriptor missing") + } + help := commandUsageWithRenderer(descriptor, renderer) + if !strings.Contains(help, renderer.DisplayCommand("adopt", "materialize", "plan", "--help")) { + t.Fatal("continuation lost the installed carrier") + } + descriptor, _ = commandDescriptorFor("adopt-materialize-plan") + guide := commandUsageWithRenderer(descriptor, renderer) + commands := guideCommands(t, guide, "Materialization input guide:", renderer) + if !reflect.DeepEqual(commands, expectedMaterializationGuideCommands()) { + t.Fatalf("published carrier command operands drifted: %v", commands) + } + for _, denial := range []string{"NOT an additive patch", "binding's specPath must equal its source's requirementsPath", "durability_unknown", "not an execution"} { + if !strings.Contains(guide, denial) { + t.Fatalf("guide lost required boundary: %s", denial) + } + } + descriptor, _ = commandDescriptorFor("requirement-authoring-plan") + authoring := commandUsageWithRenderer(descriptor, renderer) + commands = guideCommands(t, authoring, "Requirement authoring input guide:", renderer) + if !reflect.DeepEqual(commands, [][]string{{"requirement-authoring-plan", "--input", ""}}) || + !strings.Contains(authoring, " "+renderer.DisplayCommand("adopt", "materialize", "plan", "--help")) { + t.Fatalf("authoring guide lost the carrier or published operands: %v", commands) + } + } +} + +func TestAdoptionInputGuideWholeChain(t *testing.T) { + for _, intent := range []string{"fresh", "audit-from-code", "code-baseline"} { + t.Run(intent, func(t *testing.T) { + root := t.TempDir() + packet := adoptionHelpPacket(t, root, intent) + payload := adoptionHelpJSON(t, packet) + _, help, _ := executeAgentWorkflowCLI(t, []string{"adopt", "materialize", "plan", "--help"}, panicReader{}, PresentationCapabilities{}) + commands := guideCommands(t, help, "Materialization input guide:", cliexec.PathRenderer()) + if !reflect.DeepEqual(commands, expectedMaterializationGuideCommands()) { + t.Fatalf("guide chain drifted: %v", commands) + } + operands := map[string]string{"": "-", "": root} + for _, command := range commands[1:4] { + report := runAdoptionHelpCLI(t, payload, fillGuideOperands(t, command, operands)...) + if report["state"] != "passed" { + t.Fatalf("child %s did not admit the real template", command[0]) + } + } + plan := runAdoptionHelpCLI(t, payload, fillGuideOperands(t, commands[4], operands)...) + if plan["planKind"] != "proofkit.adoption-materialization-plan" || plan["state"] != "ready" || plan["sourceIntent"] != intent { + t.Fatalf("wrong plan identity or trust intent: %v", plan) + } + if entries, err := os.ReadDir(root); err != nil || len(entries) != 0 { + t.Fatal("help, source planning or write planning mutated the empty repository") + } + transaction := plan["transaction"].(map[string]any) + operands[""] = transaction["transactionId"].(string) + operands[""] = transaction["desiredStateId"].(string) + receipt := runAdoptionHelpCLI(t, payload, fillGuideOperands(t, commands[5], operands)...) + if receipt["receiptKind"] != "proofkit.adoption-materialization-receipt" || receipt["state"] != "passed" { + t.Fatalf("wrong materialization receipt: %v", receipt) + } + for _, artifact := range []struct{ path, command string }{ + {"docs/specs/requests/requirements.v1.json", "requirement-source-admission"}, + {"proofkit/requirement-bindings.json", "requirement-bindings"}, + {"proofkit/test-evidence-inventory.json", "test-evidence-inventory"}, + } { + report := runAdoptionHelpCLI(t, nil, artifact.command, "--input", filepath.Join(root, artifact.path)) + if report["state"] != "passed" { + t.Fatalf("materialized %s failed re-admission", artifact.path) + } + } + bindingBytes, err := os.ReadFile(filepath.Join(root, "proofkit/requirement-bindings.json")) + if err != nil { + t.Fatal(err) + } + binding := decodeCLIJSON(t, string(bindingBytes)).(map[string]any) + route := binding["bindings"].([]any)[0].(map[string]any) + if route["scenarioId"] != "example.requests.empty" || route["witnessId"] != "example.witness.empty" || route["requirementId"] != "REQ-EXAMPLE-001" { + t.Fatal("materialization lost the scenario/requirement/witness edge") + } + for _, absent := range []string{"src/request_test.go", "docs/specs/requests/overview.md"} { + if _, err := os.Stat(filepath.Join(root, absent)); !os.IsNotExist(err) { + t.Fatalf("guide fabricated a native test or overview: %s", absent) + } + } + }) + } +} + +func TestAdoptionInputGuideRejectsBrokenEdges(t *testing.T) { + for _, mutation := range []struct { + name string + edit func(map[string]any) + want string + }{ + {"missing runtime operand", func(v map[string]any) { v["sourcePlan"] = nil }, "adoption plan"}, + {"source plan ID is not a plan", func(v map[string]any) { v["sourcePlan"] = v["sourcePlan"].(map[string]any)["planId"] }, "adoption plan"}, + {"wrong identity", func(v map[string]any) { v["requestKind"] = "example.wrong" }, "identity"}, + {"owner projection", func(v map[string]any) { adoptionHelpRequirement(v)["ownerId"] = "example.other" }, "binding requirement projection"}, + {"nonclaim projection", func(v map[string]any) { adoptionHelpRequirement(v)["nonClaims"] = []any{} }, "binding requirement projection"}, + {"source binding path", func(v map[string]any) { adoptionHelpRequirement(v)["proofBindingRefs"] = []any{"proofkit/other.json"} }, "proofBindingRefs"}, + {"inventory source edge", func(v map[string]any) { + adoptionHelpEntry(v)["sourcePath"] = "src/other_test.go" + adoptionHelpEntry(v)["selector"] = "src/other_test.go::TestRejectEmptyInput" + }, "not connected"}, + {"inventory command edge", func(v map[string]any) { adoptionHelpEntry(v)["commandRefs"] = []any{"example.unknown"} }, "not connected"}, + {"inventory witness edge", func(v map[string]any) { adoptionHelpEntry(v)["witnessRefs"] = []any{"example.unknown"} }, "not connected"}, + {"inventory requirement edge", func(v map[string]any) { adoptionHelpEntry(v)["requirementRefs"] = []any{"REQ-UNKNOWN-001"} }, "unknown requirement"}, + {"secret text", func(v map[string]any) { adoptionHelpRequirement(v)["invariant"] = "api_key=private-example-value" }, "secret"}, + } { + t.Run(mutation.name, func(t *testing.T) { + root := t.TempDir() + packet := adoptionHelpPacket(t, root, "fresh") + mutation.edit(packet) + status, stdout, stderr := executeAgentWorkflowCLI(t, []string{"adopt", "materialize", "plan", "--input", "-", "--repo-root", root}, bytes.NewReader(adoptionHelpJSON(t, packet)), PresentationCapabilities{}) + if status != 1 || stdout != "" || !strings.Contains(stderr, mutation.want) || strings.Contains(stderr, "private-example-value") { + t.Fatalf("wrong rejection: status=%d stdout=%q stderr=%q", status, stdout, stderr) + } + if entries, err := os.ReadDir(root); err != nil || len(entries) != 0 { + t.Fatal("failed plan mutated the repository") + } + }) + } +} + +func TestAdoptionInputGuideInventoryVersion(t *testing.T) { + _, help, _ := executeAgentWorkflowCLI(t, []string{"test-evidence-inventory", "--help"}, panicReader{}, PresentationCapabilities{}) + if !strings.Contains(help, "direct inventory schemaVersion=1") || strings.Contains(help, "\n schemaVersion=2\n") { + t.Fatal("inventory help confuses aggregate contract version with direct input schema") + } + packet := adoptionHelpPacket(t, t.TempDir(), "fresh") + inventory := packet["testEvidenceInventory"].(map[string]any)["record"].(map[string]any) + report := runAdoptionHelpCLI(t, adoptionHelpJSON(t, inventory), "test-evidence-inventory", "--input", "-") + if report["state"] != "passed" { + t.Fatal("published direct inventory was rejected") + } + inventory["schemaVersion"] = 2 + status, stdout, stderr := executeAgentWorkflowCLI(t, []string{"test-evidence-inventory", "--input", "-"}, bytes.NewReader(adoptionHelpJSON(t, inventory)), PresentationCapabilities{}) + if status != 1 || stdout != "" || !strings.Contains(stderr, "schemaVersion must be 1") { + t.Fatalf("direct v2 input was not rejected: status=%d stdout=%q stderr=%q", status, stdout, stderr) + } +} + +func adoptionHelpPacket(t *testing.T, root, intent string) map[string]any { + t.Helper() + status, help, stderr := executeAgentWorkflowCLI(t, []string{"adopt", "materialize", "plan", "--help"}, panicReader{}, PresentationCapabilities{}) + if status != 0 || stderr != "" { + t.Fatalf("guide status=%d stderr=%q", status, stderr) + } + sections := strings.Split(help, "```json\n") + if len(sections) != 2 { + t.Fatal("guide must publish exactly one connected JSON template") + } + input, _, closed := strings.Cut(sections[1], "\n```") + if !closed { + t.Fatal("guide template fence is not closed") + } + packet := decodeCLIJSON(t, input).(map[string]any) + if packet["sourcePlan"] != nil { + t.Fatal("template contains a fabricated source plan") + } + commands := guideCommands(t, help, "Materialization input guide:", cliexec.PathRenderer()) + if !reflect.DeepEqual(commands, expectedMaterializationGuideCommands()) { + t.Fatalf("published source-plan or continuation operands drifted: %v", commands) + } + args := fillGuideOperands(t, commands[0], map[string]string{"": root, "": intent}) + packet["sourcePlan"] = runAdoptionHelpCLI(t, nil, args...) + return packet +} + +func adoptionHelpJSON(t *testing.T, value any) []byte { + t.Helper() + data, err := json.Marshal(value) + if err != nil { + t.Fatal(err) + } + return data +} + +func runAdoptionHelpCLI(t *testing.T, input []byte, args ...string) map[string]any { + t.Helper() + status, stdout, stderr := executeAgentWorkflowCLI(t, args, bytes.NewReader(input), PresentationCapabilities{}) + if status != 0 || stderr != "" { + t.Fatalf("%v status=%d stderr=%q stdout=%q", args, status, stderr, stdout) + } + return decodeCLIJSON(t, stdout).(map[string]any) +} + +func adoptionHelpRequirement(v map[string]any) map[string]any { + return v["requirementSources"].([]any)[0].(map[string]any)["requirements"].([]any)[0].(map[string]any) +} + +func adoptionHelpEntry(v map[string]any) map[string]any { + return v["testEvidenceInventory"].(map[string]any)["record"].(map[string]any)["entries"].([]any)[0].(map[string]any) +} + +func expectedMaterializationGuideCommands() [][]string { + return [][]string{ + {"adopt", "plan", "--repo-root", "", "--mode", "", "--format", "json"}, + {"requirement-source-admission", "--input", "", "--input-pointer", "/requirementSources/0"}, + {"requirement-bindings", "--input", "", "--input-pointer", "/requirementProofBinding/record"}, + {"test-evidence-inventory", "--input", "", "--input-pointer", "/testEvidenceInventory/record"}, + {"adopt", "materialize", "plan", "--input", "", "--repo-root", ""}, + {"adopt", "materialize", "apply", "--input", "", "--repo-root", "", "--expect-transaction", "", "--expect-desired-state", ""}, + } +} + +// These guides publish simple literal route operands, not arbitrary shell code. +func guideCommands(t *testing.T, help, marker string, renderer cliexec.Renderer) [][]string { + t.Helper() + _, guide, ok := strings.Cut(help, marker) + if !ok { + t.Fatal("guide marker missing") + } + prefix := " " + renderer.DisplayCommand() + " " + var commands [][]string + for _, line := range strings.Split(guide, "\n") { + if tail, matched := strings.CutPrefix(line, prefix); matched { + args := strings.Fields(tail) + if strings.Join(args, " ") != tail { + t.Fatal("guide operands are not canonically space-delimited") + } + commands = append(commands, args) + } + } + return commands +} + +func fillGuideOperands(t *testing.T, args []string, operands map[string]string) []string { + t.Helper() + result := append([]string{}, args...) + for index, value := range result { + if replacement, ok := operands[value]; ok { + result[index] = replacement + } else if strings.ContainsAny(value, "<>") { + t.Fatalf("unbound guide operand: %s", value) + } + } + return result +} diff --git a/internal/app/authoring_input_guide_test.go b/internal/app/authoring_input_guide_test.go new file mode 100644 index 0000000..ad46633 --- /dev/null +++ b/internal/app/authoring_input_guide_test.go @@ -0,0 +1,87 @@ +package app + +import ( + "bytes" + "reflect" + "strings" + "testing" + + "github.com/research-engineering/agentic-proofkit/internal/kernel/cliexec" +) + +func TestAuthoringInputGuideBootstrapCLI(t *testing.T) { + root := t.TempDir() + materialization := adoptionHelpPacket(t, root, "audit-from-code") + source := materialization["requirementSources"].([]any)[0].(map[string]any) + candidate := source["requirements"].([]any)[0] + empty := decodeCLIJSON(t, string(adoptionHelpJSON(t, source))).(map[string]any) + empty["requirements"] = []any{} + + var canonical string + for _, args := range [][]string{ + {"requirement-authoring-plan", "--help"}, + {"requirement-authoring-plan", "-h"}, + {"help", "requirement-authoring-plan"}, + } { + status, help, stderr := executeAgentWorkflowCLI(t, args, panicReader{}, PresentationCapabilities{}) + if status != 0 || stderr != "" || len(help) > 10<<10 || !strings.Contains(help, "adopt materialize plan --help") { + t.Fatalf("authoring help status=%d stderr=%q", status, stderr) + } + if canonical != "" && canonical != help { + t.Fatal("authoring help aliases disagree") + } + canonical = help + } + parts := strings.Split(canonical, "```json\n") + if len(parts) != 2 { + t.Fatal("authoring help must contain one template, not duplicate source examples") + } + input, _, closed := strings.Cut(parts[1], "\n```") + if !closed { + t.Fatal("unclosed authoring template") + } + packet := decodeCLIJSON(t, input).(map[string]any) + if packet["mode"] != "retrospective_baseline" { + t.Fatalf("published authoring mode drifted: %v", packet["mode"]) + } + commands := guideCommands(t, canonical, "Requirement authoring input guide:", cliexec.PathRenderer()) + if !reflect.DeepEqual(commands, [][]string{{"requirement-authoring-plan", "--input", ""}}) { + t.Fatalf("wrong authoring invocation: %v", commands) + } + args := fillGuideOperands(t, commands[0], map[string]string{"": "-"}) + update := packet["candidateUpdates"].([]any)[0].(map[string]any) + if packet["currentRequirementSource"] != nil || update["candidateRequirement"] != nil { + t.Fatal("authoring template fabricated its source or candidate") + } + packet["currentRequirementSource"], update["candidateRequirement"] = empty, candidate + for _, mode := range []string{"retrospective_baseline", "pull_request_design"} { + packet["mode"] = mode + report := runAdoptionHelpCLI(t, adoptionHelpJSON(t, packet), args...) + if report["planKind"] != "proofkit.requirement-authoring-plan" || report["state"] != "passed" || report["mode"] != mode { + t.Fatalf("wrong authoring report: %v", report) + } + preview := report["nonAuthoritativeAdmissionPreview"].(map[string]any) + if preview["candidateOnly"] != true || preview["ownerReviewRequired"] != true || preview["authority"] != "candidate_only" { + t.Fatal("admitted bootstrap became product approval") + } + if !reflect.DeepEqual(preview["requirementSourcePreview"], source) { + t.Fatalf("bootstrap source mismatch:\ngot: %s\nwant: %s", adoptionHelpJSON(t, preview["requirementSourcePreview"]), adoptionHelpJSON(t, source)) + } + materialization["requirementSources"] = []any{preview["requirementSourcePreview"]} + plan := runAdoptionHelpCLI(t, adoptionHelpJSON(t, materialization), "adopt", "materialize", "plan", "--input", "-", "--repo-root", root) + if plan["state"] != "ready" || plan["sourceIntent"] != "audit-from-code" { + t.Fatal("bootstrap preview cannot feed the existing materialization route") + } + } + + // A source containing the candidate must not be treated as empty bootstrap. + packet["currentRequirementSource"] = source + status, stdout, stderr := executeAgentWorkflowCLI(t, []string{"requirement-authoring-plan", "--input", "-"}, bytes.NewReader(adoptionHelpJSON(t, packet)), PresentationCapabilities{}) + if status != 1 || stderr != "" { + t.Fatalf("duplicate add status=%d stdout=%q stderr=%q", status, stdout, stderr) + } + failed := decodeCLIJSON(t, stdout).(map[string]any) + if failed["state"] != "failed" || failed["nonAuthoritativeAdmissionPreview"] != nil { + t.Fatal("duplicate add retained a passing candidate preview") + } +} diff --git a/internal/app/cli_contract_test.go b/internal/app/cli_contract_test.go index 952362e..3bafb4a 100644 --- a/internal/app/cli_contract_test.go +++ b/internal/app/cli_contract_test.go @@ -24,7 +24,7 @@ import ( ) const ( - cliContractPublicABISHA256 = "998fa0c554256d05cdbd400e9c5a2c107cf2bf0b1cdd3c78046295a4c1d9c636" + cliContractPublicABISHA256 = "bf5359d40ce87aa1ad95ba0c8dec8a61413590e2051ca5b534ba8821a29963fc" 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 e7c05db..899f1d5 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 = "05577c1543d131fa0b1a1724e2e888be820d2c80aa85d0dd349450df8c12b4f1" +const commandContractSourceSHA256 = "9022f0da266be6e31300813712124b04cde47b2d74ab6d5ed4ddaa08f1245e92" type generatedCommandContractMetadata struct { InputContractSHA256 string @@ -12,15 +12,15 @@ type generatedCommandContractMetadata struct { } var generatedCommandContractMetadataByName = map[string]generatedCommandContractMetadata{ - "adopt-materialize-apply": {InputContractSHA256: "sha256:725f8033b3360e3bbe1aef952bfc1ef19a6d17aed86610a18f19cd4428c0ec3b", 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:d2450373b6db416d7b2561d14216ec8a03cf62794048c67b489b857ae0fc243f", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"adopt", "materialize", "apply"}}, - "adopt-materialize-plan": {InputContractSHA256: "sha256:4d0a8ac8913e7e9f3558624b800afd3df9ac6d85d320e1217016ed96f63623af", 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:065f41cd9c968a7d3c9a430f7b2d002b70a9e911e10ddddde61ab81457afe622", 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:9f5d3ecc2969b37d7be88e6f6f932d36d356d5be92daedec7613faef6f262292", 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:ac460820567586e2d9a34544c918a802c3bb06a915c62f873d53f4a34b7fa7af", 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:0b3b50e294f7f03ea14d55c0908620928651a869da43d8e687f79ed514fba09e", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"adopt", "materialize", "apply"}}, + "adopt-materialize-plan": {InputContractSHA256: "sha256:adb7a6e5789fa59350603ef93b190b5929fae682405c5396fd148698ee9625ae", 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:886301d245ed8f8f5c5e74a2b9d82d201e3981b47bee4ba325f418811622da3b", 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:52518686fdecd4fb960734154e199fd6f18a03fce2e8d1bc30fc13c5a82dc31e", 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:b945f8b420b75f2d95285a30f7496080620777f79f021d39558148cd78b1fb6c", 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:787235a8de6de6b8843bf1df48e3d3ec5f8c32a3fdec6073dfbfc7b0cfd2c717", 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"}}, @@ -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:87deb48003e246aa8bdf8cb7e60a4f34c4118d83a9e595dafcd39606e0558b82", 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:b068ae37c3e69bc6dac477590e8afbc94da5c3c6e0d1f8fd339b6dc18b16ba12", FlagChoices: map[string][]string{"--format": []string{"json", "text"}, "--tool": []string{"claude", "codex"}}, RouteTokens: []string{"integration", "check"}}, - "integration-plan": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:f437758e59ec4273d02201a4fa35503192111bbcaec7af81af0e2c65aa5d75c4", 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:05affeba05f60b981592945eefd93017e54d61a2753124d82d3f3e63e29cf7a5", 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:16bd6bae737ae676f8228daa9734a9a14645903af5675a1c43774376955fd055", FlagChoices: map[string][]string{"--format": []string{"json", "text"}, "--tool": []string{"claude", "codex"}}, RouteTokens: []string{"integration", "source"}}, + "integration-apply": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:42101bd8f1aa6d29fe9da3d853ba86c5437d8653f36b0854f6f7f213950e384c", 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:0f2a9d43c7941539926aea0168e7e13dbe54eeef3b9a29854b9ab674aa571012", FlagChoices: map[string][]string{"--format": []string{"json", "text"}, "--tool": []string{"claude", "codex"}}, RouteTokens: []string{"integration", "check"}}, + "integration-plan": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:c324f00d5c0e9398a80c9fe46e7e128a5c0af16f56aaa15e03ba1c0b4372394f", 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:07c1686d40a0261b81a385704c64daad90c7b74fd6ab15f248ab9e327855a4fd", 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:cc0e6ed2647246725ecaf7be038cbc803f56a15d4d8d8a44168b88e1050e04ee", 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:83d0dc27b7dc7bb6ff13501cc2d6b71c602337127383395d61d8b84d1ba6e810", 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:49efaa4c7ad543bb35dde511229f3c873314d2ee33eca6a9916fa1a75fdc14a7", 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:7cb4c4fb60c8b5a37109bbd8c00d567749f7d181bbc905d8bc58155f139c44cb", 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:3f802ac3fac6762ede51f0e0a151f16dc10b4a20344a3887b3ee8bae43ce94f2", FlagChoices: map[string][]string{}, RouteTokens: []string{"proof-receipt-admission"}}, @@ -65,7 +65,7 @@ var generatedCommandContractMetadataByName = map[string]generatedCommandContract "rendered-artifact-freshness": {InputContractSHA256: "sha256:be4f53ef1307b4c16bb15a945f8021473b5a215961f3d38f6f591a0240da91f3", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.rendered-artifact-freshness.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:c9a763142d11daca913672b0bab517767d35e275cd7c8ccb2bb360e6fc8f7425", FlagChoices: map[string][]string{}, RouteTokens: []string{"rendered-artifact-freshness"}}, "repo-profile-admission": {InputContractSHA256: "sha256:3a7331d66195dbdc9f672d380efe8fdb9d1d2e36a764b8bc912dccdd81b0e965", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.repo-profile-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:36d2116fa144aa86d7b9d0ac59b89ad04fb97c85a11f3f65efb7311506761fbd", FlagChoices: map[string][]string{}, RouteTokens: []string{"repo-profile-admission"}}, "repository-inventory": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:4a6fc5b5ef55090854e70927494d220afdee0ae234de4f61a720a6018865f02f", FlagChoices: map[string][]string{}, RouteTokens: []string{"repository-inventory"}}, - "requirement-authoring-plan": {InputContractSHA256: "sha256:208d7d47109dee1ec355ae3970937690ae528a9cc0cb0eb885d7cc72d843f1e8", 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:e995d289c4a44310add784bbafa5a3a50ec305c3809a89506dd3b49914fbe28f", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-authoring-plan"}}, + "requirement-authoring-plan": {InputContractSHA256: "sha256:f7a53affaa19700bf8a6b38e93c746455b120caee1c42cb28a0b4c2894d75657", 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:68d0efbbc60482db6c028322c33aa3d644a84ef44553a61d350ab80259b1c35d", FlagChoices: map[string][]string{}, RouteTokens: []string{"requirement-authoring-plan"}}, "requirement-bindings": {InputContractSHA256: "sha256:4771b7ed1e23b20c983060deb8f8e65391052f0e5a61cf0f5c67c0e73b8fc5dd", 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"}}, @@ -89,12 +89,12 @@ 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:c3e6da4b1ff65997722e4ffa9b4a1b975fa3694d0d9bef0e894d97433d873861", 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:1090e81498d1d375cc6e58b5d99e04a509480c9a6e6caa699f130310f40404a6", FlagChoices: map[string][]string{}, RouteTokens: []string{"self-check"}}, + "self-check": {InputContractSHA256: "sha256:b028bc7d213b47d310c191f3d5606d09baef4e4a234fe1e59a46a32ead73473b", 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:980e285adbf349a9abf7028e3fe7d1b12da6ced973d1864babd97c568f0f6ce1", 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:6b6c2875b6476e63a1911e7d6112d9999df2babbee969f84abc4c9e4b470c933", 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:e9e0eb66cebca3b99fe5036fb2e7327a9284934ed76f58818d18094d0546fc52", 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"}}, "status": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:41b27b34d8ed1eef3446b355a35774a540bfe63e0481a75cf90889739e38cf88", FlagChoices: map[string][]string{"--color": []string{"auto", "never"}, "--format": []string{"json", "text"}}, RouteTokens: []string{"status"}}, - "test-evidence-inventory": {InputContractSHA256: "sha256:c2e26a02e127eec069275c9afa902a20cb9e062f4008db35e042d8fe0e3fdd60", InputSchemaSummary: []string{"schemaVersion=2", "root-shape-only definition proofkit.test-evidence-inventory.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:06fd4be6a34e73708f8539fed04ec41b2a4c9c79cf0e0dcc311496c0e6aafcbf", FlagChoices: map[string][]string{}, RouteTokens: []string{"test-evidence-inventory"}}, + "test-evidence-inventory": {InputContractSHA256: "sha256:d11ad2ff13c3c6ec562a7cb6aee38fee190f0a3ac7ff5415df19fb6a9376af7f", InputSchemaSummary: []string{"aggregate input contract v2; direct inventory schemaVersion=1; proof-binding-derived projection schemaVersion=2; discovery-draft projection schemaVersion=1", "root-shape-only definition proofkit.test-evidence-inventory.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:06fd4be6a34e73708f8539fed04ec41b2a4c9c79cf0e0dcc311496c0e6aafcbf", FlagChoices: map[string][]string{}, RouteTokens: []string{"test-evidence-inventory"}}, "text-policy": {InputContractSHA256: "sha256:afc366b0bcdcb33d7b85d3347f832cb647de68cb737cfa81a52e55f5b4901038", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.text-policy.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:94031bd5ee75115e80ebe10cb8c3c7b77806dc349721ba550cacaa99ff8184e4", FlagChoices: map[string][]string{}, RouteTokens: []string{"text-policy"}}, "typescript-public-api-surfaces": {InputContractSHA256: "sha256:11ddef5ae86c8f5ff9fcb8cfac540c8ea680cd61233e54e9ba74563b28020e2a", InputSchemaSummary: []string{"schemaVersion=1", "machineContract=public_api_surfaces", "entries[].packageManifestPath", "entries[].packageName", "entries[].exportKey", "entries[].exportConditions[] (non-empty, sorted unique by condition)", "entries[].exportConditions[].condition", "entries[].exportConditions[].path", "entries[].exportConditions[].sourcePath (declared and canonical target .ts/.mts/.cts)", "entries[].runtimeExports[]", "entries[].typeExports[]", "entries[].deniedExportKeys[] (optional)", "sourceGrammar=fail_closed_restricted_typescript_exports_v1", "maxSourceFileBytes=8388608", "maxPackageManifestBytes=262144", "maxAggregateFileReadBytes=67108864", "root-shape-only definition proofkit.typescript-public-api-surfaces.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:f26372435db16de436cd5483089d06d3723496574b28d80de97ee36ccddf1587", FlagChoices: map[string][]string{}, RouteTokens: []string{"typescript-public-api-surfaces"}}, "view": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:4374a9dce8f1f7046f9ce0a2b06bcd5ed9e768cdb1b18e0a2d97859cd8c474b1", FlagChoices: map[string][]string{}, RouteTokens: []string{"view"}}, diff --git a/internal/app/command_help.go b/internal/app/command_help.go index 12f4aaa..d28be33 100644 --- a/internal/app/command_help.go +++ b/internal/app/command_help.go @@ -5,7 +5,9 @@ import ( "slices" "strings" + "github.com/research-engineering/agentic-proofkit/internal/command/adoptionmaterialization" "github.com/research-engineering/agentic-proofkit/internal/command/capabilitymapadmission" + "github.com/research-engineering/agentic-proofkit/internal/command/requirementauthoringplan" "github.com/research-engineering/agentic-proofkit/internal/kernel/cliexec" ) @@ -62,7 +64,7 @@ func commandUsageWithRenderer(descriptor commandDescriptor, renderer cliexec.Ren if descriptor.name == "stack-preset" || descriptor.name == "requirement-source-admission" { lines = append(lines, "", - "Continue with the installed README first-valid-input example:", + "Optional human first-input example (CLI guidance is available below):", " Path: node_modules/@research-engineering/agentic-proofkit/README.md", ) } @@ -110,12 +112,43 @@ func commandUsageWithRenderer(descriptor commandDescriptor, renderer cliexec.Ren if descriptor.name == "capability-map-admission" { lines = append(lines, "", strings.TrimSuffix(capabilitymapadmission.InputGuide, "\n")) } + if descriptor.name == "adopt-materialize-plan" { + lines = append(lines, "", strings.TrimSuffix(adoptionmaterialization.InputGuide(renderer), "\n")) + } + if descriptor.name == "requirement-authoring-plan" { + lines = append(lines, "", strings.TrimSuffix(requirementauthoringplan.InputGuide(renderer), "\n")) + } + if pointer := adoptionGuidePointer(descriptor.name); pointer != "" { + lines = append(lines, "", "CLI authoring continuation:", + " "+renderer.DisplayCommand("adopt", "materialize", "plan", "--help"), + " The connected request template contains this command's input at "+pointer+".", + " Read its prerequisites and non-claims before adapting the synthetic values.") + } + if descriptor.name == "adopt-plan" || descriptor.name == "native-evidence-guidance" || descriptor.name == "capability-map-admission" || descriptor.name == "stack-preset" { + lines = append(lines, "", "After owner review and native witness design:", + " "+renderer.DisplayCommand("adopt", "materialize", "plan", "--help"), + " This guide connects requirements, scenarios, test routes and write planning.", + " Do not promote candidates or infer execution from admission success.") + } lines = append(lines, "", "Public contract:") lines = append(lines, " CLI command routing, root JSON shapes, output modes, exit codes, and flags are owned by proofkit/cli-contract.v2.json.") lines = append(lines, " Nested field semantics remain owned by native command admission.") return strings.Join(lines, "\n") + "\n" } +func adoptionGuidePointer(command string) string { + switch command { + case "requirement-source-admission": + return "/requirementSources/0" + case "requirement-bindings": + return "/requirementProofBinding/record" + case "test-evidence-inventory": + return "/testEvidenceInventory/record" + default: + return "" + } +} + func installedCommandUsageLine(descriptor commandDescriptor) string { return installedCommandUsageLineWithRenderer(descriptor, cliexec.PathRenderer()) } diff --git a/internal/app/compact_contract_wire_observations_test.go b/internal/app/compact_contract_wire_observations_test.go index 76e2892..bd6e5ef 100644 --- a/internal/app/compact_contract_wire_observations_test.go +++ b/internal/app/compact_contract_wire_observations_test.go @@ -145,6 +145,15 @@ func TestCurrentCompactV2WireSemanticsMatchFrozenVersionEdge(t *testing.T) { frozenValue = compactWithoutContractFreshnessDigests(t, frozenValue, key+" frozen") currentValue = compactWithoutContractFreshnessDigests(t, currentValue, key+" current") } + if key == "test-evidence-inventory|input|union" { + record := clonePublicABIRecord(currentValue.(map[string]any)) + input, err := normalizeInventoryInputGuideContract(record["contract"].(map[string]any)) + if err != nil { + t.Fatal(err) + } + record["contract"] = input + currentValue = record + } if !compactJSONEqual(frozenValue, currentValue) { pointers := []string{} collectUncoveredCompactWireDiffs(frozenValue, true, currentValue, true, "", map[string]string{}, map[string]struct{}{}, &pointers) diff --git a/internal/app/integration_version_edge_test.go b/internal/app/integration_version_edge_test.go index 4555473..dd7d3ee 100644 --- a/internal/app/integration_version_edge_test.go +++ b/internal/app/integration_version_edge_test.go @@ -77,6 +77,9 @@ const managedReplayPolicy = "Replay requires a retained generation-2 terminal re func verifyManagedIntegrationPublicABIDiff(frozen frozenPublicABI, current map[string]any) error { current = clonePublicABIRecord(current) + if err := normalizeInventoryInputGuidePublicABIDelta(current); err != nil { + return err + } if err := normalizeProjectContextPublicABIDelta(current); err != nil { return err } diff --git a/internal/app/testdata/compact-current-production-consumers.json b/internal/app/testdata/compact-current-production-consumers.json index cc99cfe..7ed83a0 100644 --- a/internal/app/testdata/compact-current-production-consumers.json +++ b/internal/app/testdata/compact-current-production-consumers.json @@ -61,6 +61,7 @@ "internal/tools/pythonpackage/main.go", "internal/tools/pythonpackage/verify.go", "internal/tools/pythonpackage/workflow_carrier.go", + "internal/tools/workflowsmoke/adoption_input_guide.go", "internal/tools/workflowsmoke/integration_smoke.go", "internal/tools/workflowsmoke/process.go", "internal/tools/workflowsmoke/project_navigation_smoke.go", diff --git a/internal/command/adoptionmaterialization/input_guide.go b/internal/command/adoptionmaterialization/input_guide.go new file mode 100644 index 0000000..55f627b --- /dev/null +++ b/internal/command/adoptionmaterialization/input_guide.go @@ -0,0 +1,182 @@ +package adoptionmaterialization + +import ( + "strings" + + "github.com/research-engineering/agentic-proofkit/internal/kernel/cliexec" +) + +// InputGuide owns the connected authoring example, not child admission policy. +func InputGuide(renderer cliexec.Renderer) string { + return strings.ReplaceAll(inputGuide, "{{cli}}", renderer.DisplayCommand()) +} + +const inputGuide = `Materialization input guide: + Before materialization: the owner must review the requirement meanings and + the agent must inspect the declared native test paths, selectors and commands. + Source-only drafting and admission may precede native witness design; use + /requirementSources/0 below without inventing tests or approval. These are + materialization prerequisites, not prerequisites for reading the source shape. + No package documentation is needed for this example. It is not an exhaustive + schema and does not authorize writes, approve meaning or prove test execution. + + First obtain the source plan: + {{cli}} adopt plan --repo-root --mode --format json + Choose fresh for owner-supplied intent, audit-from-code for untrusted code, + or code-baseline only when the caller explicitly trusts the existing behavior. + Replace sourcePlan: null below with that entire output object, not its planId, + a report wrapper, file path or fabricated digest. Null deliberately fails + admission. Keep the plan's trust declaration; changing a mode is an owner + decision, not a way to make validation pass. This plan is routing context, + not proof that the inspected code or witness is current. + + All other values below form one fictional, connected example. Replace them + consistently with reviewed repository facts; never copy a fictional witness + or passing result into a real inventory. Missing intent or witnesses means + stop and ask the owner or use native-evidence-guidance to design the missing + check. A declared_semantic_falsifier_route is a declaration, not an execution + receipt. Run native checks separately under repository authority. + + Store your completed packet in a caller-selected scratch JSON file. Its + requirementSources are requirement-source-admission inputs, not source reports. + Binding and inventory record fields are raw owner inputs, not passed reports. + Paths are repository-relative; stable IDs and ID lists must obey admission. + Sort unique ID/path lists. Preserve identical requirementId, ownerId, + claimLevel and nonClaims across the source and binding projection. + The binding's specPath must equal its source's requirementsPath; the source + does not have a specPath field. + Each inventory entry's requirementRefs, witnessRefs, commandRefs and sourcePath + must meet on a binding route. Select environmentClasses from actual repository + policy; local-go here is an example, not an inferred default for your project. + For multiple scenarios, add binding rows and tests, not duplicate requirements. + The packet is the complete desired project record set, NOT an additive patch. + In an existing project retain every still-required source and its connected + binding/inventory rows; replacing shared records with one edited source alone + can remove other sources from routing. Review removals as well as additions. + +Connected request template (sourcePlan is the only runtime placeholder): +` + "```json\n" + `{ + "schemaVersion": 1, + "requestKind": "proofkit.adoption-materialization-request", + "requestId": "example.materialization", + "projectId": "example.project", + "sourcePlan": null, + "requirementSources": [{ + "schemaVersion": 1, + "sourceId": "example.requirements", + "specPackagePath": "docs/specs/requests", + "overviewPath": "docs/specs/requests/overview.md", + "requirementsPath": "docs/specs/requests/requirements.v1.json", + "nonClaims": ["Synthetic source; no product intent or execution is proven."], + "requirements": [{ + "requirementId": "REQ-EXAMPLE-001", + "ownerId": "example.backend", + "invariant": "Empty requests are rejected.", + "claimLevel": "blocking", + "riskClass": "high", + "lifecycle": {"state": "active", "replacementRequirementIds": [], "evidenceRefs": []}, + "nonClaimRefs": [], + "nonClaims": ["Synthetic requirement; no native execution is proven."], + "proofBindingRefs": ["proofkit/requirement-bindings.json"], + "updatePolicy": {"requiresImpactDeclaration": true, "requiresProofBindingReview": true, "reviewOwnerId": "example.backend"} + }] + }], + "requirementProofBinding": { + "path": "proofkit/requirement-bindings.json", + "record": { + "schemaVersion": 1, + "bindingId": "example.bindings", + "requirements": [{ + "requirementId": "REQ-EXAMPLE-001", + "ownerId": "example.backend", + "claimLevel": "blocking", + "proofState": "witness_backed", + "specPath": "docs/specs/requests/requirements.v1.json", + "nonClaims": ["Synthetic requirement; no native execution is proven."] + }], + "bindings": [{ + "requirementId": "REQ-EXAMPLE-001", + "scenarioId": "example.requests.empty", + "witnessId": "example.witness.empty", + "witnessKind": "contract", + "witnessPath": "src/request_test.go", + "commandIds": ["example.test.requests"], + "environmentClasses": ["local-go"] + }], + "witnessCommands": [{ + "commandId": "example.test.requests", + "command": "go test ./src -run TestRejectEmptyInput", + "environmentClasses": ["local-go"] + }], + "selection": {"changedPaths": [], "ownerIds": [], "requirementIds": []}, + "nonClaims": ["Synthetic binding; witness_backed declares a route, not a passing run."] + } + }, + "testEvidenceInventory": { + "path": "proofkit/test-evidence-inventory.json", + "record": { + "schemaVersion": 1, + "inventoryId": "example.inventory", + "authority": "caller_owned_inventory", + "entries": [{ + "testId": "example.test.empty", + "ownerId": "example.backend", + "sourcePath": "src/request_test.go", + "selector": "src/request_test.go::TestRejectEmptyInput", + "evidenceClass": "declared_semantic_falsifier_route", + "requirementRefs": ["REQ-EXAMPLE-001"], + "ownerInvariantRefs": [], + "commandRefs": ["example.test.requests"], + "witnessRefs": ["example.witness.empty"], + "falsifier": { + "falsifierId": "example.falsifier.empty", + "negativeCaseId": "example.case.empty", + "wrongImplementationClassId": "example.wrong.accept-empty", + "dominanceGroup": "example.requests.empty", + "supersedes": [] + }, + "oracle": { + "oracleId": "example.oracle.empty", + "oracleKind": "negative_exit_and_diagnostic", + "expectedPublicOutcome": "The test fails when empty input is accepted.", + "assertionSummary": "TestRejectEmptyInput asserts rejection; an accepting mutant must fail that assertion." + }, + "nonClaims": ["Synthetic test route; mutation execution is not asserted."] + }], + "nonClaims": ["Synthetic inventory; no test execution or coverage is proven."] + } + }, + "nonClaims": ["Synthetic template; review scope, intent and witnesses before materialization."] +} +` + "```\n" + ` +Validate the completed packet (replace with its scratch file path): + {{cli}} requirement-source-admission --input --input-pointer /requirementSources/0 + {{cli}} requirement-bindings --input --input-pointer /requirementProofBinding/record + {{cli}} test-evidence-inventory --input --input-pointer /testEvidenceInventory/record + {{cli}} adopt materialize plan --input --repo-root + Child success requires exit 0 and state passed; the plan requires exit 0 and + state ready. On rejection, repair the named input field and rerun admission; + never replace a failed report with invented passed JSON. A sourcePlan failure + requires a new adopt plan output, not hand-edited task text or hashes. + If a parent error only says a child must pass, invoke that child directly + with its pointer above to inspect its ruleResults and diagnostics. For more + sources repeat requirement-source-admission for /requirementSources/. + +Before applying, review transaction.operations and manifest. Only requirement + sources, the binding record, test inventory and project routing manifest are + materialized. No test, overview, execution receipt or CI script is generated. + Source and test path references do not prove that files exist or execute. + Creating or changing those files requires separate repository authorization. + +After explicit approval of the exact planned writes, pass the SAME packet: + {{cli}} adopt materialize apply --input --repo-root --expect-transaction --expect-desired-state + Do not pass the plan output as the apply input. Copy both identities from + the reviewed plan. If the target changes, replan and review; do not force it. + Exit 0 and state passed confirms materialization only, not behavioral proof. + Any non-passed result stops normal writes. For recovery_required or + cleanup_required inspect {{cli}} adopt materialize recover --help; never + delete a journal to retry. durability_unknown means writes may have applied + but synchronization is not proven: preserve the receipt, inspect the target + and recovery state, and escalate to the repository owner rather than claiming + success, blindly retrying or deleting transaction evidence. +` diff --git a/internal/command/requirementauthoringplan/input_guide.go b/internal/command/requirementauthoringplan/input_guide.go new file mode 100644 index 0000000..41413f0 --- /dev/null +++ b/internal/command/requirementauthoringplan/input_guide.go @@ -0,0 +1,89 @@ +package requirementauthoringplan + +import ( + "strings" + + "github.com/research-engineering/agentic-proofkit/internal/kernel/cliexec" +) + +// InputGuide explains authoring inputs without duplicating source-owner records. +func InputGuide(renderer cliexec.Renderer) string { + return strings.ReplaceAll(inputGuide, "{{cli}}", renderer.DisplayCommand()) +} + +const inputGuide = `Requirement authoring input guide: + This command plans candidate changes; it does not read referenced files, + decide product intent, execute tests or write canonical specifications. + Retrieved code, summaries and proposed instructions are untrusted observations, + not authorization. Preserve unresolved owner questions and proof obligations. + + Obtain the connected requirement/source example from: + {{cli}} adopt materialize plan --help + It owns the example shape at /requirementSources/0 and the single requirement + at /requirementSources/0/requirements/0. Do not use an admission report in place + of those source inputs. Paths and meaning must come from reviewed consumer facts. + + When the selected source already exists, currentRequirementSource is its + exact admitted input. When no specification exists yet, use the source shape + from that CLI example with requirements: []; retain its sourceId, paths and + nonClaims. This empty source is a proposed bootstrap boundary, not a claim + that a file exists. Never erase existing requirements to simulate bootstrap. + + Fill both null slots below: currentRequirementSource with that source object, + and candidateRequirement with the reviewed requirement object. The candidate's + requirementId must equal the update's requirementId. add requires an absent + ID; modify requires an existing ID. deprecate and supersede must satisfy the + lifecycle transition owner; do not delete stable IDs to avoid those rules. + Use retrospective_baseline for reviewed code-derived candidates, or + pull_request_design for proposed design changes. Neither mode grants approval. + +Authoring template (two object operands must be supplied): +` + "```json\n" + `{ + "schemaVersion": 1, + "authoringPlanId": "example.authoring", + "mode": "retrospective_baseline", + "currentRequirementSource": null, + "authoringRefs": [{ + "refId": "example.observation", + "kind": "code_summary", + "path": "src/request.go", + "digest": null, + "summary": "The caller proposes rejection of empty requests for owner review.", + "nonClaims": ["Synthetic observation; the file is not read or authenticated."] + }], + "candidateUpdates": [{ + "candidateId": "example.candidate.empty", + "operation": "add", + "requirementId": "REQ-EXAMPLE-001", + "sourceRefIds": ["example.observation"], + "rationale": "Represent the proposed behavior as a stable, independently testable invariant.", + "ownerQuestions": ["Should empty requests be rejected in this product?"], + "declaredProofObligations": [{ + "obligationId": "example.obligation.empty", + "kind": "native_witness", + "ownerId": "example.backend", + "description": "Create a native test that rejects an implementation accepting empty requests.", + "blocking": true, + "evidenceRefs": [] + }], + "candidateRequirement": null + }], + "nonClaims": ["Synthetic authoring plan; owner approval and test execution are not proven."] +} +` + "```\n" + ` +Admit the completed packet (use - for stdin): + {{cli}} requirement-authoring-plan --input + Exit 0 and state passed mean a candidate source and transition are admissible, + not that owner questions or proof obligations have been resolved. Inspect + ownerReviewPlan, promotionPreconditions and ruleResults; failures require + correcting the supplied candidates or source, not hand-editing report state. + On success the source is at: + /nonAuthoritativeAdmissionPreview/requirementSourcePreview + Only after the repository owner approves the meanings, use that source input + as requirementSources[0] in the materialization packet. Keep one record per + stable requirement ID; separate scenario/test rows may reference that ID. + This is not an additive project patch: retain all still-required sources and + shared binding/inventory rows when updating an existing project. + Design native checks using native-evidence-guidance, connect actual witnesses, + then use adopt materialize plan --help to review the exact write transaction. +` diff --git a/internal/command/stackpreset/preset_ids_generated.go b/internal/command/stackpreset/preset_ids_generated.go index f6a3651..644ed2f 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 = "05577c1543d131fa0b1a1724e2e888be820d2c80aa85d0dd349450df8c12b4f1" +const presetContractSourceSHA256 = "9022f0da266be6e31300813712124b04cde47b2d74ab6d5ed4ddaa08f1245e92" 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 0aca9e4..a34e7aa 100644 --- a/internal/tools/releasechange/record_test.go +++ b/internal/tools/releasechange/record_test.go @@ -197,8 +197,8 @@ func TestCurrentChangeRecordNamesReviewedSemanticChanges(t *testing.T) { var currentBreakingChanges = []Change{} var currentAdditions = []Change{ - {ChangeID: "proofkit.source-model.metadata-parity", Summary: "Preserve independently authored source and requirement non-claims, external non-claim references and proof-binding paths in the private candidate source model. Keep named local references distinct from direct text and external identity; reject duplicate effective boundary statements within one scope and require binding paths for active blocking requirements. Private codec fields, projections, budgets and tests preserve member or shared-profile ownership. Public source admission and CLI contracts remain unchanged; no source-v2 cutover is included."}, - {ChangeID: "proofkit.source-model.boundary-closure", Summary: "Preserve complete dotted identifiers when mapping model errors to exact lexical owner spans, reject invalid UTF-8 paths before formatting, and admit completed grouped invariants through the shared text policy. Bound expanded projection work before repeated payload traversal while retaining item-before-text rejection precedence and exact admitted costs. Require the minimum JSON depth to cover complete canonical output and locate effective non-claim duplicates at their participating reference owner. Negative controls cover hidden payload copying, mixed budgets, Unicode paths, diagnostic identity collisions and composition. Published platform requirements are unchanged."}, + {ChangeID: "proofkit.adoption.cli-input-guidance", Summary: "Expose a connected requirement, scenario, witness and test-inventory example through on-demand CLI help. Source, binding and inventory help route to exact input pointers instead of requiring package documentation. Authoring help explains empty-source bootstrap, candidate-only previews and lifecycle owners without duplicating the source template. Guidance preserves full desired-state materialization, trust declarations, exact write approval and recovery distinctions; no consumer policy or native execution is introduced."}, + {ChangeID: "proofkit.adoption.guide-contract-witnesses", Summary: "Distinguish direct inventory schemaVersion 1 from aggregate contract v2 and projection-specific versions in input help. Check actual published source-plan and continuation operands, original authoring mode, installed launcher rendering and connected examples through native and installed-carrier witnesses. Preserve accepted input semantics and platform requirements; a declared test route still does not prove execution, approval or complete coverage."}, } var currentMigrationSteps = []string{} @@ -221,7 +221,7 @@ func validateCurrentChangeRecord(record Record, notes string) error { func currentExpectedReleaseNotes() string { lines := []string{ - "# @research-engineering/agentic-proofkit 0.14.4", + "# @research-engineering/agentic-proofkit 0.14.5", "", "## Breaking Contract Changes", "", @@ -274,7 +274,7 @@ func currentExpectedReleaseNotes() string { "Primary npm channel:", "", "```bash", - "npm install --save-dev --save-exact @research-engineering/agentic-proofkit@0.14.4", + "npm install --save-dev --save-exact @research-engineering/agentic-proofkit@0.14.5", "```", "", "Pre-1.0 npm consumers must keep this dependency exact-pinned.", @@ -286,7 +286,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.3 with `npm install --save-dev --save-exact @research-engineering/agentic-proofkit@0.14.3`.", + "- Pin npm consumers to the previous admitted version 0.14.4 with `npm install --save-dev --save-exact @research-engineering/agentic-proofkit@0.14.4`.", "- Treat local package artifacts as candidates until registry identity is proven.", ) return strings.Join(lines, "\n") + "\n" diff --git a/internal/tools/workflowsmoke/adoption_input_guide.go b/internal/tools/workflowsmoke/adoption_input_guide.go new file mode 100644 index 0000000..197bcc9 --- /dev/null +++ b/internal/tools/workflowsmoke/adoption_input_guide.go @@ -0,0 +1,192 @@ +package workflowsmoke + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "os" + "strings" + + "github.com/research-engineering/agentic-proofkit/internal/command/adoptionmaterialization" + "github.com/research-engineering/agentic-proofkit/internal/command/requirementauthoringplan" + "github.com/research-engineering/agentic-proofkit/internal/kernel/admission" +) + +// The installed help is the fixture producer; native owners validate its actual +// operands. The runner keeps launcher execution outside this proof owner. +func verifyAdoptionInputGuides(ctx context.Context, run Runner) (returnErr error) { + root, err := os.MkdirTemp("", "proofkit-input-guide-") + if err != nil { + return err + } + defer func() { returnErr = errors.Join(returnErr, os.RemoveAll(root)) }() + help, err := invoke(ctx, run, "materialization input guide", unreadInvocation("adopt", "materialize", "plan", "--help")) + if err != nil { + return err + } + packet, prefix, err := installedGuideTemplate(help.Stdout, "adopt materialize plan") + if err != nil { + return err + } + if packet["sourcePlan"] != nil { + return fmt.Errorf("installed guide must not fabricate a source plan") + } + planArgs := []string{"adopt", "plan", "--repo-root", "", "--mode", "", "--format", "json"} + if !hasGuideCommand(help.Stdout, prefix, planArgs) { + return fmt.Errorf("installed guide lost its exact source-plan command") + } + planArgs[3], planArgs[5] = root, "fresh" + sourcePlan, err := invoke(ctx, run, "guide source plan", unreadInvocation(planArgs...)) + if err != nil { + return err + } + packet["sourcePlan"], err = admission.DecodeJSON(bytes.NewReader(sourcePlan.Stdout), defaultMaximumStdoutBytes) + if err != nil { + return err + } + input, err := json.Marshal(packet) + if err != nil { + return err + } + for _, child := range []struct{ command, pointer, kind string }{ + {"requirement-source-admission", "/requirementSources/0", "proofkit.requirement-source-admission"}, + {"requirement-bindings", "/requirementProofBinding/record", "proofkit.requirement-proof-bindings"}, + {"test-evidence-inventory", "/testEvidenceInventory/record", "proofkit.test-evidence-inventory"}, + } { + args := []string{child.command, "--input", "", "--input-pointer", child.pointer} + if !hasGuideCommand(help.Stdout, prefix, args) { + return fmt.Errorf("installed guide lost a child command or pointer") + } + args[2] = "-" + result, err := invoke(ctx, run, "guide child input", bytesInvocation(input, args...)) + if err != nil { + return err + } + value, err := admission.DecodeJSON(bytes.NewReader(result.Stdout), defaultMaximumStdoutBytes) + if err != nil { + return err + } + report, ok := value.(map[string]any) + if !ok || report["reportKind"] != child.kind || report["state"] != "passed" { + return fmt.Errorf("installed guide child must return its own passing report") + } + } + expected, err := adoptionmaterialization.BuildPlan(ctx, packet, root) + if err != nil { + return fmt.Errorf("installed guide packet must pass native planning: %w", err) + } + if expected.JSONValue()["state"] != "ready" { + return fmt.Errorf("installed guide packet must produce a ready native plan") + } + args := []string{"adopt", "materialize", "plan", "--input", "", "--repo-root", ""} + if !hasGuideCommand(help.Stdout, prefix, args) { + return fmt.Errorf("installed guide lost the materialization plan command") + } + args[4], args[6] = "-", root + result, err := invoke(ctx, run, "guide materialization plan", bytesInvocation(input, args...)) + if err != nil { + return err + } + if err := verifyExactJSONObject(result, expected.JSONValue(), "guide materialization plan"); err != nil { + return err + } + return verifyAuthoringInputGuide(ctx, run, packet, prefix) +} + +func verifyAuthoringInputGuide(ctx context.Context, run Runner, materialization map[string]any, prefix string) error { + help, err := invoke(ctx, run, "authoring input guide", unreadInvocation("requirement-authoring-plan", "--help")) + if err != nil { + return err + } + packet, authoringPrefix, err := installedGuideTemplate(help.Stdout, "requirement-authoring-plan") + if err != nil { + return err + } + args := []string{"requirement-authoring-plan", "--input", ""} + if authoringPrefix != prefix || !hasGuideCommand(help.Stdout, prefix, args) || packet["mode"] != "retrospective_baseline" { + return fmt.Errorf("installed authoring guide lost its carrier, mode or command") + } + sources, ok := materialization["requirementSources"].([]any) + if !ok || len(sources) != 1 { + return fmt.Errorf("installed connected template must have one source") + } + source, ok := sources[0].(map[string]any) + if !ok { + return fmt.Errorf("installed connected template source must be an object") + } + requirements, ok := source["requirements"].([]any) + if !ok || len(requirements) != 1 { + return fmt.Errorf("installed connected template must have one requirement") + } + updates, ok := packet["candidateUpdates"].([]any) + if !ok || len(updates) != 1 { + return fmt.Errorf("installed authoring template must have one candidate update") + } + update, ok := updates[0].(map[string]any) + if !ok || packet["currentRequirementSource"] != nil || update["candidateRequirement"] != nil { + return fmt.Errorf("installed authoring template must declare its two object operands") + } + emptySource := make(map[string]any, len(source)) + for key, value := range source { + emptySource[key] = value + } + emptySource["requirements"] = []any{} + packet["currentRequirementSource"], update["candidateRequirement"] = emptySource, requirements[0] + expected, exitCode, err := requirementauthoringplan.Build(packet) + if err != nil { + return fmt.Errorf("installed authoring template must pass native admission: %w", err) + } + preview, ok := expected["nonAuthoritativeAdmissionPreview"].(map[string]any) + if exitCode != 0 || !ok || preview["candidateOnly"] != true || preview["ownerReviewRequired"] != true { + return fmt.Errorf("installed authoring template must preserve its source without granting approval") + } + previewBytes, err := json.Marshal(preview["requirementSourcePreview"]) + if err != nil { + return err + } + if err := verifyExactJSONObject(Result{Stdout: previewBytes}, source, "guide authoring source preservation"); err != nil { + return err + } + input, err := json.Marshal(packet) + if err != nil { + return err + } + args[2] = "-" + result, err := invoke(ctx, run, "guide authoring input", bytesInvocation(input, args...)) + if err != nil { + return err + } + return verifyExactJSONObject(result, expected, "guide authoring input") +} + +func installedGuideTemplate(help []byte, route string) (map[string]any, string, error) { + _, invocation, ok := strings.Cut(string(help), "\nInstalled invocation:\n ") + line, _, _ := strings.Cut(invocation, "\n") + index := strings.LastIndex(line, " "+route) + if !ok || index <= 0 { + return nil, "", fmt.Errorf("installed guide must identify its carrier") + } + sections := bytes.Split(help, []byte("```json\n")) + if len(sections) != 2 { + return nil, "", fmt.Errorf("installed guide must contain one connected JSON template") + } + input, _, closed := bytes.Cut(sections[1], []byte("\n```")) + if !closed { + return nil, "", fmt.Errorf("installed guide template must have a closing fence") + } + value, err := admission.DecodeJSON(bytes.NewReader(input), defaultMaximumStdoutBytes) + if err != nil { + return nil, "", err + } + packet, ok := value.(map[string]any) + if !ok { + return nil, "", fmt.Errorf("installed guide template must be an object") + } + return packet, line[:index], nil +} + +func hasGuideCommand(help []byte, prefix string, args []string) bool { + return bytes.Count(help, []byte("\n "+prefix+" "+strings.Join(args, " ")+"\n")) == 1 +} diff --git a/internal/tools/workflowsmoke/workflow_smoke.go b/internal/tools/workflowsmoke/workflow_smoke.go index 483e3d0..396f57f 100644 --- a/internal/tools/workflowsmoke/workflow_smoke.go +++ b/internal/tools/workflowsmoke/workflow_smoke.go @@ -160,6 +160,9 @@ func Verify(ctx context.Context, run Runner) error { if err := verifyCapabilityInputGuide(ctx, run); err != nil { return err } + if err := verifyAdoptionInputGuides(ctx, run); err != nil { + return err + } return verifyIntegrations(ctx, run) } diff --git a/internal/tools/workflowsmoke/workflow_smoke_test.go b/internal/tools/workflowsmoke/workflow_smoke_test.go index a510d62..47b19e7 100644 --- a/internal/tools/workflowsmoke/workflow_smoke_test.go +++ b/internal/tools/workflowsmoke/workflow_smoke_test.go @@ -46,6 +46,13 @@ func TestVerifyRejectsCarrierContractMutations(t *testing.T) { stdinTrustMode string apply func(workflowsmoke.Result) workflowsmoke.Result }{ + {name: "materialization guide omitted", match: "adopt materialize plan --help", apply: replaceStdout("Usage:\n")}, + {name: "materialization guide initial command", match: "adopt materialize plan --help", apply: replaceStdoutFragment("adopt plan --repo-root ", "adopt plann --repo-root ")}, + {name: "materialization guide source plan dropped", match: "adopt plan --repo-root ", matchPrefix: true, apply: replaceStdout(`{}`)}, + {name: "materialization guide child report dropped", match: "requirement-source-admission --input - --input-pointer /requirementSources/0", apply: replaceStdout(`{}`)}, + {name: "authoring guide invalid mode", match: "requirement-authoring-plan --help", apply: replaceStdoutFragment(`"mode": "retrospective_baseline"`, `"mode": "invalid_mode"`)}, + {name: "authoring guide lost carrier", match: "requirement-authoring-plan --help", apply: replaceStdoutFragment(" agentic-proofkit requirement-authoring-plan --input ", " wrong-proofkit requirement-authoring-plan --input ")}, + {name: "authoring guide report dropped", match: "requirement-authoring-plan --input -", apply: replaceStdout(`{"state":"passed"}`)}, {name: "capability help example changed", match: "capability-map-admission --help", apply: replaceStdoutFragment(`"dirtyState": "unknown"`, `"dirtyState": "clean"`)}, {name: "capability input runner drops report", match: "capability-map-admission --input -", apply: replaceStdout(`{"state":"passed"}`)}, {name: "capability baseline runner drops report", match: "capability-map-admission --input -", stdinTrustMode: "code_baseline", apply: replaceStdout(`{"state":"passed"}`)}, diff --git a/package-lock.json b/package-lock.json index 53a48ba..c260c40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@research-engineering/agentic-proofkit", - "version": "0.14.4", + "version": "0.14.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@research-engineering/agentic-proofkit", - "version": "0.14.4", + "version": "0.14.5", "cpu": [ "arm64", "x64" diff --git a/package.json b/package.json index 7fa268a..0d3431c 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.4", + "version": "0.14.5", "type": "module", "license": "MIT", "sideEffects": false, diff --git a/proofkit/cli-contract.v2.json b/proofkit/cli-contract.v2.json index f4896f9..95f64d9 100644 --- a/proofkit/cli-contract.v2.json +++ b/proofkit/cli-contract.v2.json @@ -112,7 +112,7 @@ "rootDefinitionDigest": "sha256:3dbf760a8f8b0a4093c1dd4edc23367c5ef8027b31c18a01490afb657fcc03ad", "nativeSource": { "path": "internal/command/adoptionmaterialization", - "canonicalDigest": "sha256:f9bd5797a95e7d620a3f0dadf814d917ded15599dabb0fc6530a2f785bcc03b4", + "canonicalDigest": "sha256:60d2a22d78bde9016931c03b85f3ccd41f83e6569d44cc9216fb561093846591", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -143,12 +143,12 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, { "path": "internal/command/adoptionmaterialization", - "canonicalDigest": "sha256:f9bd5797a95e7d620a3f0dadf814d917ded15599dabb0fc6530a2f785bcc03b4", + "canonicalDigest": "sha256:60d2a22d78bde9016931c03b85f3ccd41f83e6569d44cc9216fb561093846591", "evidenceClass": "source_checkout" }, { @@ -251,7 +251,7 @@ "rootDefinitionDigest": "sha256:c18995fb310dbde102bea231d83ec3756ffdf863199acbc88237471d06278c9e", "nativeSource": { "path": "internal/command/adoptionmaterialization", - "canonicalDigest": "sha256:f9bd5797a95e7d620a3f0dadf814d917ded15599dabb0fc6530a2f785bcc03b4", + "canonicalDigest": "sha256:60d2a22d78bde9016931c03b85f3ccd41f83e6569d44cc9216fb561093846591", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -282,12 +282,12 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, { "path": "internal/command/adoptionmaterialization", - "canonicalDigest": "sha256:f9bd5797a95e7d620a3f0dadf814d917ded15599dabb0fc6530a2f785bcc03b4", + "canonicalDigest": "sha256:60d2a22d78bde9016931c03b85f3ccd41f83e6569d44cc9216fb561093846591", "evidenceClass": "source_checkout" }, { @@ -396,12 +396,12 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, { "path": "internal/command/adoptionmaterialization", - "canonicalDigest": "sha256:f9bd5797a95e7d620a3f0dadf814d917ded15599dabb0fc6530a2f785bcc03b4", + "canonicalDigest": "sha256:60d2a22d78bde9016931c03b85f3ccd41f83e6569d44cc9216fb561093846591", "evidenceClass": "source_checkout" }, { @@ -1187,7 +1187,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, { @@ -2506,7 +2506,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, { @@ -2612,7 +2612,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, { @@ -2740,7 +2740,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, { @@ -2867,7 +2867,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, { @@ -2965,7 +2965,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, { @@ -3640,7 +3640,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, { @@ -4653,7 +4653,7 @@ "rootDefinitionDigest": "sha256:86f8f4913b5316caa63c40d79125199b282d108cbd8b9711db21c2023d1a4ca4", "nativeSource": { "path": "internal/command/requirementauthoringplan", - "canonicalDigest": "sha256:ec094da2d08e360992266402ec87fc361ef6b34023a91139015493d277bd3e76", + "canonicalDigest": "sha256:8f6b7c3cbac7c379f8f0534489961d49a8a0ee5e9c737f0d2d8fc9826b747f19", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -4681,7 +4681,7 @@ "rootDefinitionDigest": "sha256:c2264058757e40789005b7cf7417906461f064b9b9bca0c5ddda86704e3b36cc", "nativeSource": { "path": "internal/command/requirementauthoringplan", - "canonicalDigest": "sha256:ec094da2d08e360992266402ec87fc361ef6b34023a91139015493d277bd3e76", + "canonicalDigest": "sha256:8f6b7c3cbac7c379f8f0534489961d49a8a0ee5e9c737f0d2d8fc9826b747f19", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { @@ -6935,7 +6935,7 @@ "rootDefinitionDigest": "sha256:3c842174dff5361e7f83166469b832805e05aa314b073c16234b5b64e346281e", "nativeSource": { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -6964,7 +6964,7 @@ "rootDefinitionDigest": "sha256:0ea95e277ebe44cd2de42c29b47c38686ac0b6b390d8965367437b3fe138e209", "nativeSource": { "path": "internal/app", - "canonicalDigest": "sha256:8c026a951843152a375c87ec5297ccd238db56e1226e0377d698cf1b7227cb87", + "canonicalDigest": "sha256:5281d748b5692d4aaa08653e6564caa46bf5a709eb375363f84122e51e324ee0", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { @@ -7416,7 +7416,7 @@ "evidenceClass": "source_checkout" }, "compatibilitySummary": [ - "schemaVersion=2", + "aggregate input contract v2; direct inventory schemaVersion=1; proof-binding-derived projection schemaVersion=2; discovery-draft projection schemaVersion=1", "root-shape-only definition proofkit.test-evidence-inventory.input.v2.root-shape; nested fields, types, and cardinalities are non-claims" ], "ownerRequirementRefs": [ diff --git a/release/change-record.v2.json b/release/change-record.v2.json index c2986b3..1b6c2a9 100644 --- a/release/change-record.v2.json +++ b/release/change-record.v2.json @@ -1,17 +1,17 @@ { "schemaVersion": 2, - "previousVersion": "0.14.3", - "version": "0.14.4", + "previousVersion": "0.14.4", + "version": "0.14.5", "changeClass": "compatible", "breakingChanges": [], "additions": [ { - "changeId": "proofkit.source-model.metadata-parity", - "summary": "Preserve independently authored source and requirement non-claims, external non-claim references and proof-binding paths in the private candidate source model. Keep named local references distinct from direct text and external identity; reject duplicate effective boundary statements within one scope and require binding paths for active blocking requirements. Private codec fields, projections, budgets and tests preserve member or shared-profile ownership. Public source admission and CLI contracts remain unchanged; no source-v2 cutover is included." + "changeId": "proofkit.adoption.cli-input-guidance", + "summary": "Expose a connected requirement, scenario, witness and test-inventory example through on-demand CLI help. Source, binding and inventory help route to exact input pointers instead of requiring package documentation. Authoring help explains empty-source bootstrap, candidate-only previews and lifecycle owners without duplicating the source template. Guidance preserves full desired-state materialization, trust declarations, exact write approval and recovery distinctions; no consumer policy or native execution is introduced." }, { - "changeId": "proofkit.source-model.boundary-closure", - "summary": "Preserve complete dotted identifiers when mapping model errors to exact lexical owner spans, reject invalid UTF-8 paths before formatting, and admit completed grouped invariants through the shared text policy. Bound expanded projection work before repeated payload traversal while retaining item-before-text rejection precedence and exact admitted costs. Require the minimum JSON depth to cover complete canonical output and locate effective non-claim duplicates at their participating reference owner. Negative controls cover hidden payload copying, mixed budgets, Unicode paths, diagnostic identity collisions and composition. Published platform requirements are unchanged." + "changeId": "proofkit.adoption.guide-contract-witnesses", + "summary": "Distinguish direct inventory schemaVersion 1 from aggregate contract v2 and projection-specific versions in input help. Check actual published source-plan and continuation operands, original authoring mode, installed launcher rendering and connected examples through native and installed-carrier witnesses. Preserve accepted input semantics and platform requirements; a declared test route still does not prove execution, approval or complete coverage." } ], "migration": {