From a9c026c563988d8e59a7a4a635d2bf97d96446f0 Mon Sep 17 00:00:00 2001 From: 0xLeif Date: Wed, 29 Jul 2026 11:19:11 -0600 Subject: [PATCH] fix(examples): harden guided checkpoint reuse --- .specsync/change-sequence.json | 4 +- .../approvals.json | 26 +++ .../change.md | 24 ++ .../context.md | 12 + .../state.json | 37 +++ .../tasks.md | 12 + .../testing.md | 13 ++ .../verification-attempts.json | 20 ++ .../verification.json | 221 ++++++++++++++++++ Scripts/test-examples.sh | 40 ++++ docs/use-cases.md | 3 +- examples/agent-memory/run.sh | 58 +++-- examples/release-pipeline/run.sh | 58 +++-- examples/test-support/aps-large-inventory.sh | 13 ++ 14 files changed, 501 insertions(+), 40 deletions(-) create mode 100644 .specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/approvals.json create mode 100644 .specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/change.md create mode 100644 .specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/context.md create mode 100644 .specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/state.json create mode 100644 .specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/tasks.md create mode 100644 .specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/testing.md create mode 100644 .specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/verification-attempts.json create mode 100644 .specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/verification.json create mode 100755 examples/test-support/aps-large-inventory.sh diff --git a/.specsync/change-sequence.json b/.specsync/change-sequence.json index 06f4bab..1075346 100644 --- a/.specsync/change-sequence.json +++ b/.specsync/change-sequence.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "sequence": 66, - "id": "CHG-0066-document-and-ignore-repository-local-state-roots-generated-by-guided-examples", + "sequence": 67, + "id": "CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key", "acknowledged_collisions": [] } diff --git a/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/approvals.json b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/approvals.json new file mode 100644 index 0000000..09130e9 --- /dev/null +++ b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/approvals.json @@ -0,0 +1,26 @@ +{ + "approvals": [ + { + "gate": "definition", + "actor": "codex", + "timestamp": 1785344997, + "digest": "1136a3520ccdeba574c86d51b475f2c8ce62bf9884a00912d2a953a66df330de", + "note": "Approve focused post-merge hardening for the three exact-head review findings." + }, + { + "gate": "definition", + "actor": "codex", + "timestamp": 1785345134, + "digest": "e46442bc80076cc2e8164db72a55b525f8790fb53ac48b42df94700062cda652", + "note": "Approve completed hardening artifacts and regression plan before verification." + }, + { + "gate": "acceptance", + "actor": "codex", + "timestamp": 1785345506, + "digest": "25b882f0fc37bd81e359e927143b8f08b4f3b8342e29bd3feda209283cc02868", + "note": "Closing approval after full verification of post-merge example hardening." + } + ], + "reopenings": [] +} diff --git a/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/change.md b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/change.md new file mode 100644 index 0000000..f7e8d50 --- /dev/null +++ b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/change.md @@ -0,0 +1,24 @@ +--- +id: CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key +state: accepted +type: bug_fix +base_commit: 488b54ab6170c05fa44aa43842aac14aad9c5133 +--- + +# Harden guided examples against secret dumps, large schemas, and incompatible key collisions + +## Intent + +Harden guided examples against secret dumps, large schemas, and incompatible key collisions + +## Affected Canonical Specs + +- `aps-cli` + +## Acceptance Criteria + +- Agent startup avoids full-state dumps; both helpers consume complete key inventories; incompatible pre-existing checkpoint keys fail safely; regression tests cover each boundary. + +## No-spec Rationale + +This hardens runnable examples and documentation without changing the aps CLI contract. diff --git a/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/context.md b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/context.md new file mode 100644 index 0000000..c711cd2 --- /dev/null +++ b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/context.md @@ -0,0 +1,12 @@ +--- +change: CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key +artifact: context +--- + +# Context + +PR #140 merged its guided workflows after all required checks passed. An +exact-head automated review completed at the merge boundary and identified +three remaining example-only hazards: the agent startup guide still dumped all +registered values, `grep -q` could short-circuit a large key inventory under +`pipefail`, and name-only reuse could accept incompatible checkpoint keys. diff --git a/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/state.json b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/state.json new file mode 100644 index 0000000..8fe4fc0 --- /dev/null +++ b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/state.json @@ -0,0 +1,37 @@ +{ + "schema_version": 1, + "id": "CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key", + "slug": "harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key", + "title": "Harden guided examples against secret dumps, large schemas, and incompatible key collisions", + "description": "Harden guided examples against secret dumps, large schemas, and incompatible key collisions", + "kind": "bug_fix", + "state": "accepted", + "canonical_applied": true, + "base_commit": "488b54ab6170c05fa44aa43842aac14aad9c5133", + "created_at": 1785344967, + "updated_at": 1785345506, + "affected_specs": [ + "aps-cli" + ], + "affected_paths": [ + "examples", + "docs/use-cases.md", + "Scripts/test-examples.sh", + ".specsync/change-sequence.json" + ], + "no_spec_change": true, + "no_spec_change_rationale": "This hardens runnable examples and documentation without changing the aps CLI contract.", + "acceptance_criteria": [ + "Agent startup avoids full-state dumps; both helpers consume complete key inventories; incompatible pre-existing checkpoint keys fail safely; regression tests cover each boundary." + ], + "selected_artifacts": [ + "context", + "testing", + "tasks" + ], + "dependencies": [], + "answers": { + "architecture_risk": "no", + "public_contract": "no" + } +} diff --git a/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/tasks.md b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/tasks.md new file mode 100644 index 0000000..d383278 --- /dev/null +++ b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/tasks.md @@ -0,0 +1,12 @@ +--- +change: CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key +artifact: tasks +--- + +# Tasks + +- [x] Replace the unfiltered agent startup dump with checkpoint-only inspection. +- [x] Make key inventory membership consume the complete producer output. +- [x] Validate type, storage, documentation, and backing path before reusing keys. +- [x] Add regressions for secrets, large inventories, and incompatible collisions. +- [x] Run and record full verification. diff --git a/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/testing.md b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/testing.md new file mode 100644 index 0000000..cb11b81 --- /dev/null +++ b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/testing.md @@ -0,0 +1,13 @@ +--- +change: CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key +artifact: testing +--- + +# Testing + +- Run the example contract against fresh and resumed roots. +- Seed incompatible keys and require both scripts to fail before mutation. +- Seed enough keys to exceed a pipe buffer and verify compatible example keys + are still detected without a duplicate-add attempt. +- Assert documentation contains no unfiltered agent startup dump. +- Run the complete Fledge verification lane. diff --git a/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/verification-attempts.json b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/verification-attempts.json new file mode 100644 index 0000000..4831462 --- /dev/null +++ b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/verification-attempts.json @@ -0,0 +1,20 @@ +{ + "schema_version": 1, + "attempts": [ + { + "timestamp": 1785345494, + "commit": "488b54ab6170c05fa44aa43842aac14aad9c5133", + "contract_digest": "e46442bc80076cc2e8164db72a55b525f8790fb53ac48b42df94700062cda652", + "workspace_digest": "f1d9e2f19fbc0c3e7a85f829430170652e5e2026f43b6fe446d0fd01c4a120b8", + "passed": true, + "commands": [ + { + "command": "fledge lanes run verify", + "success": true, + "exit_code": 0 + } + ], + "requirement_ids": [] + } + ] +} diff --git a/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/verification.json b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/verification.json new file mode 100644 index 0000000..95aa6e3 --- /dev/null +++ b/.specsync/changes/CHG-0067-harden-guided-examples-against-secret-dumps-large-schemas-and-incompatible-key/verification.json @@ -0,0 +1,221 @@ +{ + "timestamp": 1785345494, + "commit": "488b54ab6170c05fa44aa43842aac14aad9c5133", + "contract_digest": "e46442bc80076cc2e8164db72a55b525f8790fb53ac48b42df94700062cda652", + "workspace_digest": "f1d9e2f19fbc0c3e7a85f829430170652e5e2026f43b6fe446d0fd01c4a120b8", + "acceptance_input_digest": "2ecdef8985fc489301982a84c22a137ced274582bc9afb26035dc7dcd7794164", + "acceptance_manifest": { + "schema_version": 1, + "entries": [ + { + "path": ".specsync/change-sequence.json", + "kind": "file", + "mode": 33188, + "payload_digest": "bab8321cd16bb8dd8c785ad2ede1f7f049e2d53ca2d823b114f5e7a95db6b5dd", + "entry_digest": "9ca4ac23f4f0ea546874fe95ee4db5b99943a9a5cca4c1d29bb666625c873951", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "Scripts/test-examples.sh", + "kind": "file", + "mode": 33261, + "payload_digest": "a43d76bc4d122e3a9897aa01371b70ba90185aa505e4c417b3a746597498f653", + "entry_digest": "1f4ef607a636aa5df64ff95dc560344a446f362eefe86b3967c668cf83e12113", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "docs/use-cases.md", + "kind": "file", + "mode": 33188, + "payload_digest": "90b0c8f87c93914bc225b600fe5f6e5c534cd209399c1f6bdce98d7de54bd10e", + "entry_digest": "e6f0e44cf33489e91ebfce4d0fba9a92246f0f1aec55334eeb7109bf789a616c", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples", + "kind": "non_file", + "mode": 0, + "payload_digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "entry_digest": "222691076df03c78a85394fbaafacc1a4e8eb7c79d764fa2dbf4e36ea49f084c", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples/README.md", + "kind": "file", + "mode": 33188, + "payload_digest": "515eca101228280f147a1d97354a086809b6e96760ee7aa794defc8be9f8e979", + "entry_digest": "14b962d6e8d77622755a865ffa4ed432f951537752f5f1d01a24028e84a96914", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples/agent-memory/README.md", + "kind": "file", + "mode": 33188, + "payload_digest": "86e3ee21bd27ec8101812d50bba3edb03d852524181f131671ead1efac7b4ade", + "entry_digest": "685026fda5c58ad6e141a1d66b8b5d424b4d9f2df775f8839f215c8a85889c6b", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples/agent-memory/run.sh", + "kind": "file", + "mode": 33261, + "payload_digest": "e5361f8fa07ad556c8506777b657965a350656ec11e7001add35a84f55444bb0", + "entry_digest": "7fed75dabbd4617c8adaeeb2e0125fa8afda747e9f2450a41f8a8e6c54b8284b", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples/github-actions/README.md", + "kind": "file", + "mode": 33188, + "payload_digest": "c38ee1f4c766b68c5b64cb75fc4ad36f2588488729bf420d3608f000d96d1266", + "entry_digest": "085dad5ca9204d4cda918d424c87104aca198d59f6c42edfe203b56944dd1536", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples/github-actions/workflow.yml", + "kind": "file", + "mode": 33188, + "payload_digest": "26e85c734d251f9fe460b8be1612de0ff532d5f51056d8ac95ec1cba36b1b87d", + "entry_digest": "7dad8764a2e11a0c8bb4d416a7b08b3e2f6d3304ef5f2460cf5705cafdd85362", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples/release-pipeline/README.md", + "kind": "file", + "mode": 33188, + "payload_digest": "b29bdf02f8861bfdfa0f4616a09ae672ac4bd293863eaa01c36015d7806ab7be", + "entry_digest": "3f080322f506676594e4e6324c3b8e7470984b56b6455d5559f4b03ea265424e", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples/release-pipeline/run.sh", + "kind": "file", + "mode": 33261, + "payload_digest": "caeccf638274810f023d93a6820bfd253b4a9f9e39242c08b1a7d59ff00ab8d4", + "entry_digest": "fa1ae717cfdd080cec854830d45c7966be988d7a58548645a6fd61544b562989", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples/swift-harness/README.md", + "kind": "file", + "mode": 33188, + "payload_digest": "6291350f1909dad08b4a8b2e51c9edd66e734604ab3de006d5f5df9939ebebb5", + "entry_digest": "fb33e3d033f74f4c1cdb1d34c6bd16ca5abf101cbd2f4c141d5798b57a5a5fd9", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples/swift-harness/StateHarness.swift", + "kind": "file", + "mode": 33188, + "payload_digest": "55085dea0290145f4b6e70ad1106b5d3da13005b7a82b659870c4f3e6092f935", + "entry_digest": "fbd78e671ee4ed799c9e3578026453fe722bb957f82314f3c246a22eabc5858e", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples/swift-harness/run.sh", + "kind": "file", + "mode": 33261, + "payload_digest": "ce35f02373ad9cf8141b2d2e88079f8359a5b4190c1326fae00d03e0263d503e", + "entry_digest": "40fa9f70e75f797f8014e04773981a555ae67826b9309c3de41e978758dde990", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "examples/test-support/aps-large-inventory.sh", + "kind": "file", + "mode": 33261, + "payload_digest": "be6dfa982ae7447ccdda170ae85b7cb9bc3f63597b08210abc76a43653f54b7d", + "entry_digest": "62f822c6de17b3221ed473dcec6ab896c0b29e71844cc886d3acfeb897e8a3c8", + "owners": [ + "@exact:delivery" + ] + }, + { + "path": "specs/aps-cli/aps-cli.spec.md", + "kind": "file", + "mode": 33188, + "payload_digest": "6cc51c612445b96b9ad8314a1d60322749601edc1711afc2224fe2303b7c198d", + "entry_digest": "c56c9aa1007ceeb7ea1cfa011aa934fba624405b043512964ec334d4646cd65b", + "owners": [ + "aps-cli" + ] + }, + { + "path": "specs/aps-cli/context.md", + "kind": "file", + "mode": 33188, + "payload_digest": "b6b45cae19244f96b97854129adc5794b76623aa0547b5b5ba27fd20e522948f", + "entry_digest": "3d95dbf704bd5d40be4f8f4c95897cf6e5549152b4cdaab0adc43328febf8b90", + "owners": [ + "aps-cli" + ] + }, + { + "path": "specs/aps-cli/requirements.md", + "kind": "file", + "mode": 33188, + "payload_digest": "49fe2728a53958f51729fe450a942671a1f8218a2c7c02ca11126c6f8747af34", + "entry_digest": "4ffacf606390f2b8aa0efb0b0051966ed3e11365fdfd331d2965ab4a0ec00780", + "owners": [ + "aps-cli" + ] + }, + { + "path": "specs/aps-cli/tasks.md", + "kind": "file", + "mode": 33188, + "payload_digest": "83d2620192191bceccaa4a8758b8c7120300db800d7595309ddbc96ec6591525", + "entry_digest": "16ee871a59dd92753ba0d3be7f10aff13bd6a0d622abfada0d67073e6ccf0c90", + "owners": [ + "aps-cli" + ] + }, + { + "path": "specs/aps-cli/testing.md", + "kind": "file", + "mode": 33188, + "payload_digest": "adf0eef7df26d362d10062c886390a5fb518d457d84095fb3bf3e91ab7962fbc", + "entry_digest": "75ac0e1c823cbe6c2b5d97fd77e4e5f21176588ca6484893cc989c08e6d9b2cf", + "owners": [ + "aps-cli" + ] + } + ] + }, + "passed": true, + "commands": [ + { + "command": "fledge lanes run verify", + "success": true, + "exit_code": 0 + } + ], + "requirement_ids": [] +} diff --git a/Scripts/test-examples.sh b/Scripts/test-examples.sh index e2caa85..d3b8b6a 100755 --- a/Scripts/test-examples.sh +++ b/Scripts/test-examples.sh @@ -34,6 +34,23 @@ APS_HOME="$fixture_root/agent" \ test "$(APS_HOME="$fixture_root/agent" "$aps_bin" get currentIssue)" = "321" test "$(APS_HOME="$fixture_root/agent" "$aps_bin" get workingBranch)" = "agent/example" test "$(APS_HOME="$fixture_root/agent" "$aps_bin" get testsPassed)" = "true" +REAL_APS_BIN="$aps_bin" \ +APS_BIN="$repo_root/examples/test-support/aps-large-inventory.sh" \ +APS_HOME="$fixture_root/agent" \ + "$repo_root/examples/agent-memory/run.sh" > "$fixture_root/agent-large-inventory.json" +grep -Fq '"key":"currentIssue"' "$fixture_root/agent-large-inventory.json" + +collision_root="$fixture_root/agent-collision" +APS_HOME="$collision_root" "$aps_bin" key add currentIssue \ + --type String --storage FileState --path wrong-current-issue.json \ + --initial sentinel --doc "Issue currently owned by this agent" >/dev/null +if APS_BIN="$aps_bin" APS_HOME="$collision_root" \ + "$repo_root/examples/agent-memory/run.sh" > "$fixture_root/agent-collision.json" 2>/dev/null +then + echo "Agent example accepted an incompatible checkpoint key" >&2 + exit 1 +fi +test "$(APS_HOME="$collision_root" "$aps_bin" get currentIssue)" = "sentinel" APS_BIN="$aps_bin" \ APS_HOME="$fixture_root/release" \ @@ -49,6 +66,18 @@ fi test "$(APS_HOME="$fixture_root/release" "$aps_bin" get releaseVersion)" = "9.8.7" test "$(APS_HOME="$fixture_root/release" "$aps_bin" get releaseTestsPassed)" = "false" test "$(APS_HOME="$fixture_root/release" "$aps_bin" get riskVerdict)" = "pending" + +release_collision_root="$fixture_root/release-collision" +APS_HOME="$release_collision_root" "$aps_bin" key add releaseVersion \ + --type String --storage FileState --path wrong-release-version.json \ + --initial sentinel --doc "Release version under evaluation" >/dev/null +if APS_BIN="$aps_bin" APS_HOME="$release_collision_root" \ + "$repo_root/examples/release-pipeline/run.sh" > "$fixture_root/release-collision.json" 2>/dev/null +then + echo "Release example accepted an incompatible checkpoint path" >&2 + exit 1 +fi +test "$(APS_HOME="$release_collision_root" "$aps_bin" get releaseVersion)" = "sentinel" APS_BIN="$aps_bin" \ APS_HOME="$fixture_root/release" \ RELEASE_PHASE=published \ @@ -114,6 +143,17 @@ grep -Fq '../examples/' "$repo_root/docs/README.md" grep -Fq './examples/agent-memory/run.sh' "$repo_root/examples/agent-memory/README.md" grep -Fq '${APS_BIN:-aps}' "$repo_root/examples/release-pipeline/README.md" grep -Fq 'StoredState values live in platform UserDefaults' "$repo_root/docs/use-cases.md" +if grep -Fq 'aps dump --json' "$repo_root/docs/use-cases.md"; then + echo "Agent startup documentation contains an unfiltered state dump" >&2 + exit 1 +fi +if grep -Fq 'grep -Fxq' \ + "$repo_root/examples/agent-memory/run.sh" \ + "$repo_root/examples/release-pipeline/run.sh" +then + echo "Example key inventory check can short-circuit its producer" >&2 + exit 1 +fi for ignored_root in \ /.agents/ \ /.release-state/ \ diff --git a/docs/use-cases.md b/docs/use-cases.md index 68fedc5..3076a95 100644 --- a/docs/use-cases.md +++ b/docs/use-cases.md @@ -22,8 +22,7 @@ Give each agent a dedicated root: ```bash export APS_HOME="$PWD/.agents/codex" -aps schema --json -aps dump --json +APS_BIN="${APS_BIN:-aps}" ./examples/agent-memory/run.sh ``` At the start of a session, the agent rediscovers the live schema and values. At diff --git a/examples/agent-memory/run.sh b/examples/agent-memory/run.sh index 7849595..cd5fe36 100755 --- a/examples/agent-memory/run.sh +++ b/examples/agent-memory/run.sh @@ -6,9 +6,36 @@ export APS_HOME="${APS_HOME:-$PWD/.aps-agent-example}" ensure_key() { local name="$1" - shift - if ! "$aps_bin" keys --quiet | grep -Fxq "$name"; then - "$aps_bin" key add "$name" "$@" >/dev/null + local type="$2" + local path="$3" + local doc="$4" + shift 4 + local inventory + local metadata + + inventory="$("$aps_bin" keys --quiet)" + if ! printf '%s\n' "$inventory" | grep -Fx "$name" >/dev/null; then + "$aps_bin" key add "$name" \ + --type "$type" --storage FileState --path "$path" --doc "$doc" \ + "$@" >/dev/null + fi + + metadata="$("$aps_bin" key list --json)" + if ! printf '%s\n' "$metadata" | + grep -F "\"detail\":\"$doc\",\"key\":\"$name\",\"storage\":\"FileState\",\"type\":\"$type\"" >/dev/null + then + echo "Existing key '$name' is incompatible with the agent-memory checkpoint" >&2 + return 65 + fi + if ! awk -v key="$name" -v expected_path="$path" ' + index($0, "\"name\" : \"" key "\"") { in_entry = 1; found = 1 } + in_entry && index($0, "\"path\" : \"" expected_path "\"") { path_matches = 1 } + in_entry && /^ }/ { exit(path_matches ? 0 : 1) } + END { if (!found || !path_matches) exit 1 } + ' "$APS_HOME/schema.json" + then + echo "Existing key '$name' uses an incompatible backing path" >&2 + return 65 fi } @@ -25,21 +52,16 @@ emit_checkpoint() { printf ']}\n' } -ensure_key currentIssue \ - --type Int --storage FileState --path current-issue.json --initial 0 \ - --doc "Issue currently owned by this agent" -ensure_key workingBranch \ - --type String --storage FileState --path working-branch.json --initial "" \ - --doc "Branch currently owned by this agent" -ensure_key phase \ - --type String --storage FileState --path phase.json --initial idle \ - --doc "Current workflow phase" -ensure_key testsPassed \ - --type Bool --storage FileState --path tests-passed.json --initial false \ - --doc "Whether the latest verification passed" -ensure_key blocker \ - --type String --storage FileState --path blocker.json --initial "" \ - --doc "Current blocker, or an empty string" +ensure_key currentIssue Int current-issue.json \ + "Issue currently owned by this agent" --initial 0 +ensure_key workingBranch String working-branch.json \ + "Branch currently owned by this agent" --initial "" +ensure_key phase String phase.json \ + "Current workflow phase" --initial idle +ensure_key testsPassed Bool tests-passed.json \ + "Whether the latest verification passed" --initial false +ensure_key blocker String blocker.json \ + "Current blocker, or an empty string" --initial "" if [[ -n "${CURRENT_ISSUE+x}" ]]; then "$aps_bin" set currentIssue "$CURRENT_ISSUE" >/dev/null diff --git a/examples/release-pipeline/run.sh b/examples/release-pipeline/run.sh index d67c09c..c8d1ed4 100755 --- a/examples/release-pipeline/run.sh +++ b/examples/release-pipeline/run.sh @@ -6,9 +6,36 @@ export APS_HOME="${APS_HOME:-$PWD/.aps-release-example}" ensure_key() { local name="$1" - shift - if ! "$aps_bin" keys --quiet | grep -Fxq "$name"; then - "$aps_bin" key add "$name" "$@" >/dev/null + local type="$2" + local path="$3" + local doc="$4" + shift 4 + local inventory + local metadata + + inventory="$("$aps_bin" keys --quiet)" + if ! printf '%s\n' "$inventory" | grep -Fx "$name" >/dev/null; then + "$aps_bin" key add "$name" \ + --type "$type" --storage FileState --path "$path" --doc "$doc" \ + "$@" >/dev/null + fi + + metadata="$("$aps_bin" key list --json)" + if ! printf '%s\n' "$metadata" | + grep -F "\"detail\":\"$doc\",\"key\":\"$name\",\"storage\":\"FileState\",\"type\":\"$type\"" >/dev/null + then + echo "Existing key '$name' is incompatible with the release checkpoint" >&2 + return 65 + fi + if ! awk -v key="$name" -v expected_path="$path" ' + index($0, "\"name\" : \"" key "\"") { in_entry = 1; found = 1 } + in_entry && index($0, "\"path\" : \"" expected_path "\"") { path_matches = 1 } + in_entry && /^ }/ { exit(path_matches ? 0 : 1) } + END { if (!found || !path_matches) exit 1 } + ' "$APS_HOME/schema.json" + then + echo "Existing key '$name' uses an incompatible backing path" >&2 + return 65 fi } @@ -25,21 +52,16 @@ emit_checkpoint() { printf ']}\n' } -ensure_key releaseVersion \ - --type String --storage FileState --path release-version.json --initial "" \ - --doc "Release version under evaluation" -ensure_key candidateCommit \ - --type String --storage FileState --path candidate-commit.json --initial "" \ - --doc "Exact release candidate commit" -ensure_key releasePhase \ - --type String --storage FileState --path release-phase.json --initial planned \ - --doc "Current release phase" -ensure_key releaseTestsPassed \ - --type Bool --storage FileState --path release-tests-passed.json --initial false \ - --doc "Whether release verification passed" -ensure_key riskVerdict \ - --type String --storage FileState --path risk-verdict.json --initial pending \ - --doc "Latest deterministic risk verdict" +ensure_key releaseVersion String release-version.json \ + "Release version under evaluation" --initial "" +ensure_key candidateCommit String candidate-commit.json \ + "Exact release candidate commit" --initial "" +ensure_key releasePhase String release-phase.json \ + "Current release phase" --initial planned +ensure_key releaseTestsPassed Bool release-tests-passed.json \ + "Whether release verification passed" --initial false +ensure_key riskVerdict String risk-verdict.json \ + "Latest deterministic risk verdict" --initial pending candidate_changed=false if [[ -n "${RELEASE_VERSION+x}" ]]; then diff --git a/examples/test-support/aps-large-inventory.sh b/examples/test-support/aps-large-inventory.sh new file mode 100755 index 0000000..69bee6c --- /dev/null +++ b/examples/test-support/aps-large-inventory.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +real_aps_bin="${REAL_APS_BIN:?Set REAL_APS_BIN to the aps executable}" + +if [[ "${1:-}" == "keys" && "${2:-}" == "--quiet" ]]; then + "$real_aps_bin" "$@" + for ((index = 0; index < 12000; index += 1)); do + printf 'paddingKey%05d\n' "$index" + done +else + exec "$real_aps_bin" "$@" +fi