Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/specs/proofkit-spec-proof-core/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ execution receipts, and merge policy.
and emits only bounded candidate requirements, bindings, or owner guidance.
- `REQ-PROOFKIT-SPEC-018`: an authored command-family catalog covers every
public CLI command exactly once, deterministically generates the private
runtime navigation projection, and adds opt-in family help without changing
root help, per-command help, or leaf dispatch.
runtime navigation projection, and adds opt-in family help while preserving
existing help invocation forms, process channels, no-input behavior, and leaf
dispatch; descriptor and help truth remains owned by `REQ-PROOFKIT-QUALITY-004`.

## Non-Claims

Expand Down
4 changes: 2 additions & 2 deletions docs/specs/proofkit-spec-proof-core/requirements.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@
{
"requirementId": "REQ-PROOFKIT-SPEC-018",
"ownerId": "proofkit.spec-proof-core",
"invariant": "Command family navigation projects the complete public CLI command inventory from one authored family catalog into a deterministic private runtime projection, preserves existing root and per-command help and leaf dispatch, admits only opt-in family help forms, rejects missing, duplicated, oversized, unsorted, or reserved-operand-colliding families, and does not infer command selection, execute commands through family membership, or create a second command inventory owner.",
"invariant": "Command family navigation projects the complete public CLI command inventory from one authored family catalog into a deterministic private runtime projection, preserves root and per-command help invocation forms, stdout and stderr channel discipline, no-input behavior, and leaf dispatch, admits only opt-in family help forms, rejects missing, duplicated, oversized, unsorted, or reserved-operand-colliding families, and does not infer command selection, execute commands through family membership, or create a second command inventory owner.",
"claimLevel": "blocking",
"riskClass": "medium",
"proofBindingRefs": [
Expand All @@ -477,7 +477,7 @@
"NC-PROOFKIT-SPEC-018"
],
"nonClaims": [
"This requirement does not claim command recommendation, command execution through family membership, consumer policy, native witness execution, merge approval, release approval, rollout approval, or production readiness."
"This requirement does not claim byte-for-byte help text compatibility across owner-approved CLI contract corrections, command recommendation, command execution through family membership, consumer policy, native witness execution, merge approval, release approval, rollout approval, or production readiness."
],
"lifecycle": {
"state": "active",
Expand Down
5 changes: 5 additions & 0 deletions docs/specs/proofkit-supply-chain-quality/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ vulnerability absence, or consumer rollout safety by itself.
stderr discipline, generated-source hash, owner-source parity, and exact
explicit-input counts despite an unlisted poison file in the consumer working
directory.
- `REQ-PROOFKIT-QUALITY-020`: package artifact execution starts from admitted
candidate-owned output roots, rejects ambient provider or unowned release
state before mutation, stays confined to the repository across symlinks,
binds non-empty generated content to stable source and execution-context
snapshots, and emits a schema-versioned execution record.

## Non-Claims

Expand Down
13 changes: 13 additions & 0 deletions docs/specs/proofkit-supply-chain-quality/requirements.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,19 @@
"lifecycle": {"state": "active", "replacementRequirementIds": [], "evidenceRefs": []},
"deferral": null,
"updatePolicy": {"reviewOwnerId": "proofkit.supply-chain-quality", "requiresImpactDeclaration": true, "requiresProofBindingReview": true}
},
{
"requirementId": "REQ-PROOFKIT-QUALITY-020",
"ownerId": "proofkit.supply-chain-quality",
"invariant": "Package artifact execution rejects ambient provider evidence and unowned release state before mutating candidate outputs, removes only candidate-owned package, wheel, and release outputs through a repository-confined filesystem root without following symlinks outside that root, invalidates prior execution evidence, executes the canonical package pipeline against stable source, environment, and toolchain snapshots, requires a non-empty content-bound artifact snapshot, and emits a schema-versioned execution record.",
"claimLevel": "blocking",
"riskClass": "high",
"proofBindingRefs": ["proofkit/requirement-bindings.json"],
"nonClaimRefs": ["NC-PROOFKIT-QUALITY-020"],
"nonClaims": ["This requirement does not claim producer authentication, hermetic or reproducible builds, registry publication, provider evidence freshness, release approval, rollout approval, production readiness, or exact artifact closure outside downstream package and release validators."],
"lifecycle": {"state": "active", "replacementRequirementIds": [], "evidenceRefs": []},
"deferral": null,
"updatePolicy": {"reviewOwnerId": "proofkit.supply-chain-quality", "requiresImpactDeclaration": true, "requiresProofBindingReview": true}
}
]
}
43 changes: 40 additions & 3 deletions internal/app/cli_contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -680,9 +680,46 @@ func TestDescriptorFlagConstraintsMatchCommandParsers(t *testing.T) {
if _, err := parseConformanceProfileArgs([]string{"--input", "-", "--list", "--format", "markdown"}); err == nil {
t.Fatal("conformance markdown without --profile was accepted")
}
usage := commandUsageLine(commandDescriptorByName["adoption-contract-envelope"])
if !strings.Contains(usage, " --mode <mode>") || strings.Contains(usage, "[--mode <mode>]") {
t.Fatalf("required --mode rendered as optional: %s", usage)
}

func TestDescriptorFlagConstraintsAreRenderedTruthfully(t *testing.T) {
expectedConstrainedUsage := map[string]string{
"adoption-contract-envelope": "agentic-proofkit adoption-contract-envelope --input <path|-> [--agent-envelope] [--checked-scope <scope>] [--guidance-mode <mode>] [--materialization-manifest] --mode <mode> [--pilot <value>] [--touched-rule-id <id>]",
"conformance-profile": "agentic-proofkit conformance-profile --input <path|-> [--format <mode>] [--input-pointer <pointer>] (--list | --profile <value> | --verify)",
"json-report-cli-adapter-source": "agentic-proofkit json-report-cli-adapter-source [--format <mode>] --language <value>",
"requirement-browser-server": "agentic-proofkit requirement-browser-server --input <path|-> [--empty-local-environment-policy] [--host 127.0.0.1|::1] [--input-pointer <pointer>] [--local-environment-class <id>] [--open] [--port <port>] [--scope <scope>] [--serve] --view <value>",
"requirement-proof-resolver": "agentic-proofkit requirement-proof-resolver --input <path|-> [--input-pointer <pointer>] (--empty-local-environment-policy | --local-environment-class <id>)",
"stack-preset": "agentic-proofkit stack-preset --preset <value>",
"typescript-public-api-surfaces": "agentic-proofkit typescript-public-api-surfaces --input <path|-> [--input-pointer <pointer>] --repo-root <path>",
}
constrainedCount := 0
for _, descriptor := range commandDescriptors {
usageLine := commandUsageLine(descriptor)
for _, flag := range descriptor.requiredFlags {
if !strings.Contains(usageLine, " "+flag) || strings.Contains(usageLine, "["+flag) {
t.Fatalf("%s required flag %s is not rendered as required: %s", descriptor.name, flag, usageLine)
}
}
if len(descriptor.requiredFlags) > 0 || len(descriptor.exactlyOneOfFlagGroups) > 0 {
constrainedCount++
expected, admitted := expectedConstrainedUsage[descriptor.name]
if !admitted {
t.Fatalf("%s has constraints but no independent help oracle", descriptor.name)
}
if usageLine != expected {
t.Fatalf("%s constrained usage = %q, want %q", descriptor.name, usageLine, expected)
}
}
commandHelp := commandUsage(descriptor)
for _, requirement := range descriptor.flagValueRequirements {
expected := fmt.Sprintf(" %s %s requires: %s", requirement.Flag, requirement.Value, strings.Join(requirement.RequiredFlags, ", "))
if !strings.Contains(commandHelp, expected) {
t.Fatalf("%s value constraint %q is missing from command help", descriptor.name, expected)
}
}
}
if constrainedCount != len(expectedConstrainedUsage) {
t.Fatalf("constrained descriptor count = %d, independent help oracle count = %d", constrainedCount, len(expectedConstrainedUsage))
}
}

Expand Down
41 changes: 21 additions & 20 deletions internal/app/command_family_catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,10 @@ package app

import (
"bytes"
"crypto/sha256"
"encoding/hex"
"strings"
"testing"
)

const (
rootHelpSHA256 = "d8f71779e3feab2faf4ac19d81881583d857155f2d75e923dc5bcfa61de8b6bb"
perCommandHelpSetSHA256 = "9bd92c609b1f96ee4964c1c7ae3bf2a5150a2e4a9145b179f19ace11a6b15b8d"
)

func TestCommandFamilyCatalogMatchesDescriptors(t *testing.T) {
seen := map[string]string{}
for _, family := range generatedCommandFamilyCatalog().Families {
Expand Down Expand Up @@ -70,20 +63,28 @@ func TestCommandFamilyHelpFormsAreOptInAndLeafDispatchIsUnchanged(t *testing.T)
}
}

func TestExistingHelpBytesRemainOwnerApproved(t *testing.T) {
rootDigest := sha256.Sum256([]byte(usage()))
if got := hex.EncodeToString(rootDigest[:]); got != rootHelpSHA256 {
t.Fatalf("root help hash = %s, want %s", got, rootHelpSHA256)
func TestExistingHelpEntrypointsRemainCompatible(t *testing.T) {
rootHelp := usage()
if !strings.Contains(rootHelp, "agentic-proofkit help [<command>|-h|--help]") {
t.Fatal("root help omits the admitted positional command target")
}
hash := sha256.New()
for _, descriptor := range commandDescriptors {
_, _ = hash.Write([]byte(descriptor.name))
_, _ = hash.Write([]byte{0})
_, _ = hash.Write([]byte(commandUsage(descriptor)))
_, _ = hash.Write([]byte{0})
rootForms := [][]string{nil, {"help"}, {"--help"}, {"-h"}, {"help", "--help"}, {"help", "-h"}}
for _, args := range rootForms {
if got := runTextCommand(t, args); got != rootHelp {
t.Fatalf("root help entrypoint %v diverged from the root projection", args)
}
}
if got := hex.EncodeToString(hash.Sum(nil)); got != perCommandHelpSetSHA256 {
t.Fatalf("per-command help set hash = %s, want %s", got, perCommandHelpSetSHA256)
for _, descriptor := range commandDescriptors {
expected := commandUsage(descriptor)
aliases := [][]string{{"help", descriptor.name}}
if descriptor.name != "help" {
aliases = append(aliases, []string{descriptor.name, "--help"}, []string{descriptor.name, "-h"})
}
for _, args := range aliases {
if got := runTextCommand(t, args); got != expected {
t.Fatalf("per-command help %v changed across an existing alias", args)
}
}
}
}

Expand Down Expand Up @@ -111,7 +112,7 @@ func runTextCommand(t *testing.T, args []string) string {
t.Helper()
var stdout bytes.Buffer
var stderr bytes.Buffer
status := Run(t.Context(), args, strings.NewReader(""), &stdout, &stderr)
status := Run(t.Context(), args, panicReader{}, &stdout, &stderr)
if status != 0 || stderr.Len() != 0 {
t.Fatalf("Run(%v) status=%d stdout=%q stderr=%q", args, status, stdout.String(), stderr.String())
}
Expand Down
3 changes: 3 additions & 0 deletions internal/app/command_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ func commandUsage(descriptor commandDescriptor) string {
}

func commandUsageLine(descriptor commandDescriptor) string {
if descriptor.name == "help" {
return "agentic-proofkit help [<command>|-h|--help]"
}
segments := []string{"agentic-proofkit", descriptor.name}
if descriptor.input == commandInputRequired {
segments = append(segments, "--input <path|->")
Expand Down
35 changes: 15 additions & 20 deletions internal/tools/packageartifact/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,10 @@ func runWithDependencies(root string, runner Runner, dependencies orchestrationD
if err := packageartifactrecord.Invalidate(root); err != nil {
return err
}
sourceRevision, sourceDigest, err := packageartifactrecord.SourceSnapshot(root)
if err != nil {
if err := packageartifactrecord.PrepareCandidateArtifactOutputs(root); err != nil {
return err
}
artifactBaseline, err := packageartifactrecord.ArtifactEvidenceBaseline(root)
sourceRevision, sourceDigest, err := packageartifactrecord.SourceSnapshot(root)
if err != nil {
return err
}
Expand Down Expand Up @@ -103,8 +102,6 @@ func runWithDependencies(root string, runner Runner, dependencies orchestrationD
artifactEvidence, err := packageartifactrecord.ArtifactEvidenceSnapshot(root)
if err != nil {
evidenceErr = errors.Join(evidenceErr, err)
} else if artifactEvidence.FreshnessDigest == artifactBaseline.FreshnessDigest {
evidenceErr = errors.Join(evidenceErr, fmt.Errorf("package artifact command produced no fresh artifact state"))
}
if runErr == nil && exitCode != 0 {
runErr = fmt.Errorf("package artifact command returned exit code %d without an execution error", exitCode)
Expand All @@ -115,21 +112,19 @@ func runWithDependencies(root string, runner Runner, dependencies orchestrationD
}
artifactDigest := artifactEvidence.SnapshotDigest
record := packageartifactrecord.Record{
Argv: packageartifactrecord.CanonicalCommandArgv(),
ArtifactFreshnessBaselineDigest: artifactBaseline.FreshnessDigest,
ArtifactFreshnessDigest: artifactEvidence.FreshnessDigest,
ArtifactSnapshotDigest: artifactDigest,
CommandID: packageartifactrecord.CommandID,
EnvironmentDigest: environmentDigest,
ExecutionArgv: packageartifactrecord.CanonicalExecutionArgv(),
ExitCode: exitCode,
FinishedAt: finishedAt.Format(time.RFC3339Nano),
SchemaVersion: packageartifactrecord.SchemaVersion,
SourceRevision: sourceRevision,
SourceSnapshotDigest: sourceDigest,
StartedAt: startedAt.Format(time.RFC3339Nano),
Status: status,
ToolchainDigest: toolchainDigest,
Argv: packageartifactrecord.CanonicalCommandArgv(),
ArtifactSnapshotDigest: artifactDigest,
CommandID: packageartifactrecord.CommandID,
EnvironmentDigest: environmentDigest,
ExecutionArgv: packageartifactrecord.CanonicalExecutionArgv(),
ExitCode: exitCode,
FinishedAt: finishedAt.Format(time.RFC3339Nano),
SchemaVersion: packageartifactrecord.SchemaVersion,
SourceRevision: sourceRevision,
SourceSnapshotDigest: sourceDigest,
StartedAt: startedAt.Format(time.RFC3339Nano),
Status: status,
ToolchainDigest: toolchainDigest,
}
if err := packageartifactrecord.Write(root, record); err != nil {
return errors.Join(runErr, evidenceErr, err)
Expand Down
61 changes: 58 additions & 3 deletions internal/tools/packageartifact/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,18 @@ func TestRunWithDependenciesRecordsCanonicalAndExecutionArgv(t *testing.T) {
}
}

func TestRunWithDependenciesRejectsTrueRunnerWithoutArtifactMutation(t *testing.T) {
func TestRunWithDependenciesRejectsTimestampOnlyMutationOfPreexistingArtifact(t *testing.T) {
root := packageArtifactFixture(t)
writeArtifactFixture(t, root, "artifact-v1")
artifactPath := filepath.Join(root, "artifacts", "package", "package.tgz")

err := runWithDependencies(root, runnerFunc(func(string, []string) (int, error) {
if touchErr := os.Chtimes(artifactPath, time.Now(), time.Now()); !os.IsNotExist(touchErr) {
t.Fatalf("preexisting artifact remained available for timestamp-only mutation: %v", touchErr)
}
return 0, nil
}), stableDependencies())
if err == nil || !strings.Contains(err.Error(), "produced no fresh artifact state") {
if err == nil || !strings.Contains(err.Error(), "produced no artifacts") {
t.Fatalf("runWithDependencies() error = %v", err)
}
record, readErr := packageartifactrecord.Read(root)
Expand All @@ -77,7 +81,58 @@ func TestRunWithDependenciesRejectsTrueRunnerWithoutArtifactMutation(t *testing.
t.Fatalf("record result = %s/%d, want failed/0", record.Status, record.ExitCode)
}
if validateErr := packageartifactrecord.ValidateCurrent(root, record); validateErr == nil {
t.Fatal("ValidateCurrent() accepted evidence from a no-op true runner")
t.Fatal("ValidateCurrent() accepted evidence from a timestamp-only runner")
}
}

func TestRunWithDependenciesInvalidatesPriorRecordButRetainsCandidateOutputsOnProviderEvidence(t *testing.T) {
root := packageArtifactFixture(t)
writeArtifactFixture(t, root, "candidate")
writeFileFixture(t, root, "artifacts/registry/npm-registry.json", "provider")
if err := packageartifactrecord.Write(root, packageartifactrecord.Record{Status: "passed"}); err != nil {
t.Fatal(err)
}
runnerCalled := false

err := runWithDependencies(root, runnerFunc(func(string, []string) (int, error) {
runnerCalled = true
return 0, nil
}), stableDependencies())
if err == nil || !strings.Contains(err.Error(), "rejects ambient provider evidence") {
t.Fatalf("runWithDependencies() error = %v", err)
}
if runnerCalled {
t.Fatal("runner executed after ambient provider evidence rejection")
}
if _, statErr := os.Stat(filepath.Join(root, filepath.FromSlash(packageartifactrecord.RecordPath))); !os.IsNotExist(statErr) {
t.Fatalf("prior execution record survived rejected package run: %v", statErr)
}
if content, readErr := os.ReadFile(filepath.Join(root, "artifacts/package/package.tgz")); readErr != nil || string(content) != "candidate" {
t.Fatalf("candidate output changed before provider-evidence rejection: content=%q err=%v", content, readErr)
}
}

func TestRunWithDependenciesAcceptsCleanRegenerationWithIdenticalBytes(t *testing.T) {
root := packageArtifactFixture(t)
writeArtifactFixture(t, root, "artifact-v1")
artifactPath := filepath.Join(root, "artifacts", "package", "package.tgz")

err := runWithDependencies(root, runnerFunc(func(root string, _ []string) (int, error) {
if _, statErr := os.Stat(artifactPath); !os.IsNotExist(statErr) {
t.Fatalf("preexisting artifact was not removed before regeneration: %v", statErr)
}
writeArtifactFixture(t, root, "artifact-v1")
return 0, nil
}), stableDependencies())
if err != nil {
t.Fatalf("runWithDependencies() error = %v", err)
}
record, readErr := packageartifactrecord.Read(root)
if readErr != nil {
t.Fatal(readErr)
}
if record.Status != "passed" || record.ExitCode != 0 {
t.Fatalf("record result = %s/%d, want passed/0", record.Status, record.ExitCode)
}
}

Expand Down
Loading
Loading