Compare commits

..
Author SHA1 Message Date
weichen 21e897b7f0 Record the U1 independent review verdict
ci / test-pg (pull_request) Skipped
ci / svc-realcombo (pull_request) Skipped
ci / test (pull_request) Successful in 8m33s
Why: fresh-session pi-glm review returned REVIEW PASS with no blocking
findings; report landed via transport at
docs/superpowers/reviews/2026-10-22-container-storage-u1-review.md.

Evidence: NOT_RUN (review receipt referenced)

Impact: task card only.
2026-10-22 11:19:48 +00:00
agent-exec-transport e04b7877c3 agent-exec transport 99605f17561d82dabbbf47596ef94de8/b1dcdad669fd 2026-10-22 11:13:45 +00:00
weichen e1f08650a1 Record U1 completion in the task card
Why: unit implemented via antigravity worker transport and verified by
host (80 targeted + 574 affected tests pass).

Evidence: NOT_RUN (verification receipts imported above)

Impact: task card only.
2026-10-22 10:50:26 +00:00
agent-exec-transport 2d75d101a0 agent-exec transport ed39db43befedd8a049053096d6640be/144300034285 2026-10-22 10:41:05 +00:00
weichen 6aa19e0810 Record routing-unit success receipt in U1 card
Why: the antigravity promotion for the U1 unit landed via worker
transport 735148439e276dd92445c591fe459d82 (commit 0c8bea37).

Evidence: NOT_RUN (task card only)

Impact: task card only.
2026-10-22 10:22:16 +00:00
agent-exec-transport 0c8bea3748 agent-exec transport 735148439e276dd92445c591fe459d82/43861c25f995 2026-10-22 10:20:37 +00:00
weichen a094d66e83 Disclose killed run-5 attempt in U1 card
Why: host killed run 5 before its first attempt produced work to avoid
exceeding the cursor attempt cap; private run dir removed, disclosed
here rather than silently dropped.

Evidence: NOT_RUN (task card only)

Impact: task card only.
2026-10-22 10:10:29 +00:00
weichen 0db2645f2d Record U1 attempt-4 receipt and exhaust the cursor candidate
Why: fourth attempt completed the work with passing tests but again
appended prose after the terminal JSON; cursor attempts exhausted,
moving to antigravity per role order.

Evidence: NOT_RUN (task card receipt row only)

Impact: task card only.
2026-10-22 10:09:04 +00:00
weichen 15284f8b45 Record U1 attempt-3 failed-unknown receipt
Why: cursor CLI rejected --model auto at startup with an empty model
list; standalone CLI smoke works, judged transient.

Evidence: NOT_RUN (task card receipt row only)

Impact: task card only.
2026-10-22 10:01:14 +00:00
weichen 0d0d9f91f0 Record U1 attempt-2 failed-unknown receipt
Why: worker completed and verified the unit but appended prose after
the terminal JSON, so the controller could not decode a completed
record; scene preserved, diff identical to attempt-1 patch.

Evidence: NOT_RUN (task card receipt row only)

Impact: task card only.

Refs: /tmp/container-storage-u1-run2-receipt.json
2026-10-22 09:54:51 +00:00
weichen cb72983e02 Record U1 attempt-1 failed-unknown receipt
Why: cursor attempt stalled on transport after completing the work;
scene preserved and reconciled (no external side effects, diff exported
to /tmp/container-storage-u1-attempt1.patch).

Evidence: NOT_RUN (task card receipt row only)

Impact: task card only.

Refs: /tmp/container-storage-u1-run-receipt.json
2026-10-22 09:43:35 +00:00
weichen b73592d4b9 Fill the U1 child task card with goal and scope
Why: record the U1 (environment and data-ownership contract) goal,
boundaries and completion conditions before dispatching the unit.

Evidence: NOT_RUN (task card only)

Impact: task card only.

Refs: docs/superpowers/plans/2026-10-22-container-storage-plan.md
2026-10-22 09:14:24 +00:00
weichen 3f66da541d Record the task card for feature-container-storage-plan-u1
Why: establish the authorized task workspace and its initial baseline.

Evidence: preserved precheck receipt in .task/feature-container-storage-plan-u1.md; R0=955a32de2bb49171348bad5f566cce935df19212.

Impact: task metadata only; the feature workspace starts at b360bcb974.

Refs: .task/feature-container-storage-plan-u1.md
2026-10-22 09:12:44 +00:00
weichen b360bcb974 Add container-storage initiative charter and reviewed plan
ci / svc-realcombo (pull_request) Successful in 2m47s
ci / test (pull_request) Successful in 8m54s
ci / test-pg (pull_request) Successful in 13m2s
Why: register the L1 initiative for the single-binary + containerized
database/knowledge-storage overhaul (U1-U6) and land the reviewed plan
as the charter's authoritative reference.

Evidence: NOT_RUN (planning documents only; no product change)

Impact: docs and task card only; no runtime behavior change.

Refs: docs/superpowers/plans/2026-10-22-container-storage-plan.md
2026-10-22 09:11:10 +00:00
weichen efa0b2af7e Record the task card for feature-container-storage-plan
Why: establish the authorized task workspace and its initial baseline.

Evidence: preserved precheck receipt in .task/feature-container-storage-plan.md; R0=955a32de2bb49171348bad5f566cce935df19212.

Impact: task metadata only; the feature workspace starts at 955a32de2b.

Refs: .task/feature-container-storage-plan.md
2026-10-22 09:09:22 +00:00
weichen 955a32de2b Merge pull request 'Audit follow-ups: pin-regression gate and subagent Writable preflight (M5)' (#695) from feature/audit-followups-pin-gate-m5 into main
ci / svc-realcombo (push) Successful in 2m42s
ci / test (push) Successful in 8m55s
ci / test-pg (push) Successful in 12m19s
2026-10-22 08:43:16 +00:00
weichen 2f37df890a Merge authoritative main into the task branch
ci / svc-realcombo (pull_request) Successful in 3m2s
ci / test (pull_request) Successful in 8m48s
ci / test-pg (pull_request) Successful in 12m23s
Why: include the current integration baseline before merge.

Evidence: fetched origin/main=9a80897c.

Impact: integrate upstream changes without rewriting task history.

Refs: task-sync
2026-10-22 08:30:20 +00:00
weichen 9a80897cb7 Merge pull request 'Close the merged test port governance card' (#696) from feature/card-close-20261022-080552Z into main
ci / svc-realcombo (push) Successful in 3m0s
ci / test (push) Successful in 9m4s
ci / test-pg (push) Successful in 12m51s
2026-10-22 08:20:56 +00:00
weichen 68219746be Retrigger CI after the flaky material-ack timeout
ci / svc-realcombo (pull_request) Successful in 2m44s
ci / test (pull_request) Successful in 9m20s
ci / test-pg (pull_request) Successful in 14m14s
Why: the CI test-pg timeout on TestMaterialAckAttachmentOnlyDoesNot-
CallParentModel is environmental (the product never sets a Writable
write-set, so the M5 preflight cannot trigger there; the same commit
and test pass locally in 0.05s against real PG/Redis).
Evidence: verify-materialack.json exit=0.
Impact: task metadata only.
Refs: PR #695
2026-10-22 08:15:01 +00:00
weichen 058d5b3f9c Close 1 merged task card(s) and archive them
ci / svc-realcombo (pull_request) Successful in 3m15s
ci / test (pull_request) Successful in 9m58s
ci / test-pg (pull_request) Successful in 14m16s
Why: branches merged into their immediate or root target had left their task cards open; backfill status and archive to prevent bookkeeping debt.
Evidence: fix-svc-test-port-governance.md(PR#694 into main) — merges verified via git log --merges plus Gitea API squash lookup against each card's origin target.
Impact: .task/*.md moved to .task/archive/ with status backfilled; no code or submodule change.
Refs: task.sh close (card-autoclose-r1)
2026-10-22 08:05:52 +00:00
weichen 67ffd7c89e Fold-close the task card before merge
ci / svc-realcombo (pull_request) Successful in 3m1s
ci / test-pg (pull_request) Failing after 3m47s
ci / test (pull_request) Successful in 9m32s
Why: close the task card inside its own PR so landing it needs no post-merge backfill or separate close PR.
Evidence: .task/feature-audit-followups-pin-gate-m5.md -> .task/archive/feature-audit-followups-pin-gate-m5.md; status 已完成(合入前闭合); PR #695 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/695.
Impact: task metadata only; the card is archived within PR #695 and lands with its merge commit.
Refs: task.sh close --self
2026-10-22 08:05:13 +00:00
weichen 1dbd15add0 Record the backend tip push and retrigger CI
ci / svc-realcombo (pull_request) Successful in 2m41s
ci / test (pull_request) Successful in 8m41s
ci / test-pg (pull_request) Successful in 11m53s
Why: the first CI run failed at checkout because the backend merge tip
a75783f existed only locally; it is now pushed.
Evidence: remote ref updated on Agent-System-Backend.
Impact: task metadata only.
Refs: PR #695
2026-10-22 07:52:41 +00:00
weichen 88f03f500e Record the followups review approval in the task card
ci / test (pull_request) Failing after 3s
ci / svc-realcombo (pull_request) Failing after 3s
ci / test-pg (pull_request) Failing after 3s
Why: independent review approved the pin gate and M5 fix.
Evidence: review receipt /tmp/agent-stack-b2/reviewf-run.json.
Impact: task metadata + review report.
Refs: PR #695
2026-10-22 07:51:48 +00:00
agent-exec-transport 36cc7f443b agent-exec transport 8515f314ceb985a1c630e7e8db3b8b0f/3af7c5319e38 2026-10-22 07:51:02 +00:00
weichen a2b4cf73a3 Record the merged baseline and the pre-existing governance failure
ci / test (pull_request) Failing after 3s
ci / test-pg (pull_request) Failing after 3s
ci / svc-realcombo (pull_request) Failing after 3s
Why: the branch now carries authoritative main (backend role-policy and
scratch-session work, frontend/ui pin advances); the only combination
failure is the environment-dependent svc test that reproduces on clean
main.
Evidence: verify-f-pins9.json exit=0; verify-f-rest.json exit=0;
verify-f-combo.json shows the single pre-existing failure.
Impact: task metadata only.
Refs: feature/audit-followups-pin-gate-m5
2026-10-22 07:33:47 +00:00
weichen 37e194871f Merge pull request 'Use dynamic ports in the two remaining hardcoded test bindings' (#694) from fix/svc-test-port-governance into main
ci / svc-realcombo (push) Successful in 3m0s
ci / test (push) Successful in 9m7s
ci / test-pg (push) Successful in 12m51s
2026-10-22 07:31:31 +00:00
weichen a874ee600a Merge authoritative main into the task branch
Why: include the current integration baseline before verification.

Evidence: fetched origin/main=d65195bd.

Impact: integrate upstream changes without rewriting task history.

Refs: task-sync
2026-10-22 07:25:28 +00:00
weichen b2f8c04b15 Repin modules/backend to the merged followups tip
Why: backend adopts upstream main (role-policy/HITL fixes) and the
parallel scratch-session work while keeping the M5 nested pin, so the
followups composition stays current with main.
Evidence: integration transport (gitlinkb7-run.json); pins checks and
combination targets re-run below.
Impact: one component gitlink.
Refs: feature/audit-followups-pin-gate-m5
2026-10-22 07:18:08 +00:00
weichen 679c483d00 Record the sync checkpoint
ci / svc-realcombo (pull_request) Successful in 3m19s
ci / test (pull_request) Successful in 9m27s
ci / test-pg (pull_request) Successful in 12m37s
Why: bind the task validation checkpoint to the integration baseline.

Evidence: SELF recursive component checks; sync_base=d65195bd41317e480c5a0cfa5133b0ad98a48d4b; before_record=a1e0c85c0400ce300a847da276e70d520f842844.

Impact: task metadata only.

Refs: .task/fix-svc-test-port-governance.md
2026-10-22 07:17:59 +00:00
weichen a1e0c85c04 Merge authoritative main into the task branch
Why: include the current integration baseline before verification.

Evidence: fetched origin/main=d65195bd41317e480c5a0cfa5133b0ad98a48d4b.

Impact: integrate upstream changes without rewriting task history.

Refs: task-sync
2026-10-22 07:17:09 +00:00
weichen 17ba4eab35 Record the followups verification results in the task card
ci / svc-realcombo (pull_request) Successful in 3m6s
ci / test-pg (pull_request) Failing after 3m39s
ci / test (pull_request) Successful in 10m4s
Why: pins checks green with the new gate; all other combination targets
pass; the single governance failure reproduces on clean main.
Evidence: verify-followups-{pins5,rest}.json exit=0; main reproduction
captured in-session.
Impact: task metadata only.
Refs: feature/audit-followups-pin-gate-m5
2026-10-22 07:09:14 +00:00
weichen d65195bd41 Merge pull request 'Deliver isolated no-project scratch sessions across Backend, Frontend and UI' (#691) from feature/scratch-session-general-agent into main
ci / svc-realcombo (push) Successful in 3m13s
ci / test (push) Successful in 9m30s
ci / test-pg (push) Successful in 15m22s
2026-10-22 07:08:47 +00:00
weichen a23a212015 Repin modules/backend to the nested-squad followups tip
Why: backend's nested agent-squad advances with its nested agent-native
M5 bump, keeping every container consistent with the Dev-level pins.
Evidence: integration transport (gitlinkb3b-run.json); pins checks and
full make test re-run below.
Impact: one component gitlink.
Refs: feature/audit-followups-pin-gate-m5
2026-10-22 06:52:30 +00:00
weichen a39b4f795c Advance squad and backend pins for the followups
Why: the squad container carries its nested agent-native M5 bump; the
backend container is already at its repaired tip in this history.
Evidence: integration transport (gitlinks3-run.json); pins checks and
full make test re-run below.
Impact: one component gitlink.
Refs: feature/audit-followups-pin-gate-m5
2026-10-22 06:47:42 +00:00
weichenandCursor d2dbd9dfdf Bump Backend to the PostgreSQL role policy and HITL test fixes
ci / svc-realcombo (pull_request) Successful in 3m7s
ci / test (pull_request) Successful in 9m43s
ci / test-pg (pull_request) Successful in 15m30s
Why: Dev composite test-pg failed on the role_permissions upsert from
Backend#240 and a racy HITL test; Backend#246 fixes both.

Evidence: Backend#246 CI GATE on 8cd4dd0; PostgreSQL verified by this
PR's test-pg job.

Impact: modules/backend 7bcbbdb.

Refs: Agent-System-Backend#246, Agent-System-Dev#691;
agent-exec receipt u6d-run (cursor, success)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 06:43:47 +00:00
weichen ecfa244c0a Repin modules/backend to the nested-M5 tip
Why: the backend branch advanced past its nested agent-native bump so
the composed chain carries the M5 preflight end to end.
Evidence: integration transport success (gitlinkb2h-run.json); pins
checks and full make test re-run below.
Impact: one component gitlink.
Refs: feature/audit-followups-pin-gate-m5
2026-10-22 06:34:08 +00:00
agent-exec-transport f1866903c5 agent-exec transport 461e582a25c2763a6ac100e94490eddd/c9ee6bdfd3a5
ci / svc-realcombo (pull_request) Successful in 3m7s
ci / test (pull_request) Successful in 8m36s
ci / test-pg (pull_request) Successful in 13m37s
2026-10-22 06:29:13 +00:00
weichen cb767f6035 Advance native, squad and backend pins for the followups
Why: native carries the M5 subagent preflight fix; backend adopts its
branch tip that merges upstream main (scratch sessions) on top of the
batch-2 line.
Evidence: integration transport success (gitlinkf5-run.json); nested
native pins follow in the next commit; pins checks re-run below.
Impact: two component gitlinks.
Refs: feature/audit-followups-pin-gate-m5
2026-10-22 06:25:14 +00:00
weichen 5130babad4 Fold-close the task card before merge
ci / test-pg (pull_request) Failing after 3m0s
ci / svc-realcombo (pull_request) Successful in 3m18s
ci / test (pull_request) Successful in 9m17s
Why: close the task card inside its own PR so landing it needs no post-merge backfill or separate close PR.
Evidence: .task/feature-scratch-session-general-agent.md -> .task/archive/feature-scratch-session-general-agent.md; status 已完成(合入前闭合); PR #691 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/691.
Impact: task metadata only; the card is archived within PR #691 and lands with its merge commit.
Refs: task.sh close --self
2026-10-22 06:22:02 +00:00
weichen f217a2bce6 Record the sync checkpoint
Why: bind the task validation checkpoint to the integration baseline.

Evidence: SELF recursive component checks; sync_base=33d6ef69212595c680a744549986c19bb6acd6c3; before_record=1970dccb7c8c11fb69ad739ae1338e97ca4ac0a6.

Impact: task metadata only.

Refs: .task/feature-scratch-session-general-agent.md
2026-10-22 06:21:57 +00:00
weichen 1970dccb7c Merge authoritative main into the task branch
Why: include the current integration baseline before verification.

Evidence: fetched origin/main=33d6ef69212595c680a744549986c19bb6acd6c3.

Impact: integrate upstream changes without rewriting task history.

Refs: task-sync
2026-10-22 06:21:09 +00:00
weichenandCursor 52093614fa Record the composite test run after the Frontend contracts fix
Why: make test reran after the Frontend bump; the only failure is
test_svc, whose fixed port 127.0.0.1:18794 is held by an unrelated
local python3 http.server, not by this change.
Evidence: make evidence receipt (SELF, exit 2) on b672792f.
Impact: Task card only; CI in its isolated container is authoritative.
Refs: Agent-System-Dev#691

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 06:21:05 +00:00
weichen 80e5e4559c Record the task card for fix-svc-test-port-governance
Why: establish the authorized task workspace and its initial baseline.

Evidence: preserved precheck receipt in .task/fix-svc-test-port-governance.md; R0=33d6ef69212595c680a744549986c19bb6acd6c3.

Impact: task metadata only; the feature workspace starts at 33d6ef6921.

Refs: .task/fix-svc-test-port-governance.md
2026-10-22 06:18:16 +00:00
weichen ba1d3b2b81 Add the pin-regression gate to pins checks and the PR gate
Why: PR #682 merged a stale branch that reset five component pins to
pre-batch values, silently dropping the batch-1 composition on main.
Evidence: materialized byte-exact from the reviewed transport
(gatemat-run.json); regression/forward/same/skip/missing-object cases
(verify-pingate.json); make test-governance and test-pins re-run below.
Impact: new check script wired into check-pins and task-pr-check;
PIN_REGRESSION_SKIP=1 documents deliberate downgrades.
Refs: feature/audit-followups-pin-gate-m5
2026-10-22 06:16:13 +00:00
weichen 3080c0f4af Bump the agent-native pin to the M5 subagent fix
Why: land the review-M5 preflight fix (delegate_task under a Writable
write-set is now rejected like exec/MCP/host tools).
Evidence: transport success (gitlinkm5c-run.json); native unit tests
green (verify-m5.json); gate/pins checks re-run with the pin bump.
Impact: one component gitlink.
Refs: feature/audit-followups-pin-gate-m5
2026-10-22 06:14:23 +00:00
weichenandCursor b672792f77 Bump Frontend to keep the scratch flag out of generated contracts
Why: Frontend#124 moves Conversation.scratch into a local view type so
make contracts (Core is authoritative) no longer breaks tsc.

Evidence: Frontend#124 CI GATE on 6e611c5.

Impact: modules/frontend 279b2d0.

Refs: Agent-System-Frontend#124, Agent-System-Dev#691;
agent-exec receipt u6c-run (cursor, success)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 06:14:20 +00:00
weichenandCursor 5eee6fec51 Record the composite test and the contracts regeneration finding
Why: make test passed on aa77f201, but make contracts regenerates the
frontend contracts from Core and drops Conversation.scratch, which would
fail tsc in make build; Frontend#124 moves the flag to a local view type.
Evidence: make evidence receipt (SELF) on aa77f201.
Impact: Task card only.
Refs: Agent-System-Dev#691, Agent-System-Frontend#124

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 06:07:35 +00:00
weichen 6d35f911fe Record the followups task card (pin gate + M5)
Why: carry authorization and unit plan for the audit follow-ups:
pin-regression gate and native subagent Writable preflight (M5).
Evidence: card only; no code change.
Impact: task metadata only.
Refs: feature/audit-followups-pin-gate-m5
2026-10-22 05:59:51 +00:00
weichen aa77f20184 Record the sync checkpoint
Why: bind the task validation checkpoint to the integration baseline.

Evidence: SELF recursive component checks; sync_base=b7636742fc9ea937e6030123afc608fd294861bf; before_record=1dee480dff7203aff3bd6b57661645fe0b890bce.

Impact: task metadata only.

Refs: .task/feature-scratch-session-general-agent.md
2026-10-22 05:49:12 +00:00
weichen 1dee480dff Merge authoritative main into the task branch
Why: include the current integration baseline before verification.

Evidence: fetched origin/main=b7636742fc9ea937e6030123afc608fd294861bf.

Impact: integrate upstream changes without rewriting task history.

Refs: task-sync
2026-10-22 05:48:30 +00:00
weichenandCursor ddc5bf9285 Align component pins with the merged batch-2 and scratch session Backend
Why: Dev main (#689) pins Backend to the batch-2 branch commit and newer
deps; Backend main fd69932 (Backend#245) now contains both batch-2 and
the scratch session work (Backend#241).

Evidence: Backend#245 CI GATE on 443e7af; deps match Dev main pins.

Impact: modules/backend fd69932; deps/agent-{command,intent,native,squad}
equal Dev main.

Refs: Agent-System-Backend#245, Agent-System-Dev#689, #691;
agent-exec receipt u6b-run (cursor, success)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 05:48:15 +00:00
weichenandCursor 7c97dee7c2 Record the blocked composite test on the scratch session card
ci / svc-realcombo (pull_request) Failing after 1m38s
ci / test-pg (pull_request) Failing after 3m34s
ci / test (pull_request) Failing after 7m2s
Why: make test fails at test-pins because Backend main already pins the
agent-stack batch-1 deps that Dev main has not adopted yet.
Evidence: make evidence receipt (SELF, exit 2) on 36a1d408.
Impact: Task card only; waiting for Dev #689 per queue #518.
Refs: Agent-System-Dev#691, #689, #518

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 05:26:38 +00:00
weichen 36a1d4084e Record the sync checkpoint
ci / svc-realcombo (pull_request) Failing after 1m46s
ci / test-pg (pull_request) Failing after 3m53s
ci / test (pull_request) Failing after 7m2s
Why: bind the task validation checkpoint to the integration baseline.

Evidence: SELF recursive component checks; sync_base=1de7c28c2122609dd9b6435881696492bd052a34; before_record=635459316dbd4e4a5134f4e409bed72a5dd02730.

Impact: task metadata only.

Refs: .task/feature-scratch-session-general-agent.md
2026-10-22 05:18:42 +00:00
weichen 635459316d Merge authoritative main into the task branch
Why: include the current integration baseline before verification.

Evidence: fetched origin/main=1de7c28c2122609dd9b6435881696492bd052a34.

Impact: integrate upstream changes without rewriting task history.

Refs: task-sync
2026-10-22 05:17:46 +00:00
weichenandCursor 63ff64dd7a Bump Backend, Frontend and UI to the merged scratch session work
Why: Accounts without project.create get isolated no-project
conversations; the three component PRs are merged.

Evidence: component CI on each merged PR head (GATE): Backend#241
2424f8e, Frontend#123 b02ff83, UI#60 db5129c; composite make test is
recorded on the task card.

Impact: modules/backend 029917e, modules/frontend ec1ced3,
modules/ui df1c3ed.

Refs: Agent-System-Backend#241, Agent-System-Frontend#123,
Agent-System-UI#60; agent-exec receipt u6-run (cursor, success)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 05:17:25 +00:00
weichenandCursor db37c59a32 Record review-fix attempts for scratch session gates
Why: Independent reviews blocked on project-context, bus-level rdm
gates and remaining project-only UI surfaces; each attempt must be
traceable, including the authorized UI-repo expansion.

Evidence: receipts under /tmp/scratch-session-20261022 (SELF).
Impact: Task card only.
Refs: Backend#241 Frontend#123 UI#60
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 04:04:17 +00:00
weichenandCursor 5f1d6f8e15 Record scratch session implementation attempts on the task card
Why: Every implementation attempt and its reconciliation must be traceable.
Evidence: receipts under /tmp/scratch-session-20261022 (SELF).
Impact: Task card only.
Refs: .task/feature-scratch-session-general-agent.md
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 03:22:37 +00:00
weichenandCursor 4cb94bd8d7 Record scratch-session goal and delivery endpoint on the task card
Why: Align the task card with the approved design before implementation units.
Evidence: Design approved 2026-10-22; task-start R0=a4311c63.
Impact: .task card metadata only.
Refs: docs/superpowers/specs/2026-10-22-scratch-session-general-agent-design.md
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 01:44:53 +00:00
weichenandCursor 49a22059c9 Add scratch session general-agent design and plan
Why: Record the approved design for isolated no-project conversations.
Evidence: Design sections 1-4 approved 2026-10-22; task-start R0=a4311c63.
Impact: Docs only under docs/superpowers/{specs,plans}.
Refs: docs/superpowers/specs/2026-10-22-scratch-session-general-agent-design.md
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 01:43:42 +00:00
weichen 795444c4c0 Record the task card for feature-scratch-session-general-agent
Why: establish the authorized task workspace and its initial baseline.

Evidence: preserved precheck receipt in .task/feature-scratch-session-general-agent.md; R0=a4311c6341c318d4bbf1563b3973358326086794.

Impact: task metadata only; the feature workspace starts at a4311c6341.

Refs: .task/feature-scratch-session-general-agent.md
2026-10-22 01:43:32 +00:00
41 changed files with 2847 additions and 1286 deletions
@@ -0,0 +1,162 @@
# feature/audit-followups-pin-gate-m5 任务卡
任务主记录:PR #695 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/695;状态:已完成(合入前闭合);owner:weichen
## 基线与授权
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= b7636742fc9ea937e6030123afc608fd294861bf
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-audit-followups-pin-gate-m5(worktree);分支:feature/audit-followups-pin-gate-m5
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=d2608b7436d1c2d773c607a79568c57cfaa2c32cebf35d1a0cabda9562a543c1;triage_receipt=/tmp/triage-followups-1792647600.json;receipt_sha256=01837cb87f83d50c255ee460dfa8e28ade7ed600624bea7d0c083b7653c8a5dd
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 人工决策点:仅限目标/范围/验收变更、重大取舍、高后果操作或无法自动判定的业务验收
- 当前阶段:需求拆解;当前增量:I1(待定义)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
<预期结果、受影响入口/模块/契约;明确不做什么。>
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
## 关联方向与规划
- 适用状态:待核对(无适用规划时说明原因)
- 主记录路径/版本:待核对(路径按仓库根目录定位,归档后不变;只引用唯一正文)
- 对应里程碑:待核对
## 需求分析
| ID | 描述 | 项目符合度及依据 | 优先级 | 验收条件 | 依赖 | 结论 |
|---|---|---|---|---|---|---|
| R1 | 待填写 | 待评估 | 待定 | 待定义 | 待确认 | 待评审 |
## 方案与计划
- 方案引用:待填写(含对象版本)
- 实施计划引用:待填写(含增量与需求 ID)
- 测试计划引用:待填写(含需求 ID 与验收条件)
## 阶段记录
| 阶段 | 对象版本 | 状态 | 决策/执行者 | 时间 | 依据 |
|---|---|---|---|---|---|
| 需求拆解 | 待定 | 待执行 | — | — | — |
| 需求评估 | 待定 | 待执行 | — | — | — |
| 需求表确认 | 待定 | 待执行 | — | — | — |
| 方案设计 | 待定 | 待执行 | — | — | — |
| 实施计划 | 待定 | 待执行 | — | — | — |
| 实施与自检 | 待定 | 待执行 | — | — | — |
| 正式测试与验收 | 待定 | 待执行 | — | — | — |
| 代码 Review | 待定 | 待执行 | — | — | — |
| 合入 | 待定 | 待执行 | — | — | — |
| 合入核验与收口 | 待定 | 待执行 | — | — | — |
## 探索试验与迭代
| 增量/试验 | 假设 | 范围 | 判断和停止条件 | 授权/决策依据 | 结果 | 变更影响 | 需重审补测 |
|---|---|---|---|---|---|---|---|
| 待定义 | — | — | — | 待核对 | NOT_RUN | 待评估 | 待评估 |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | b7636742fc9ea937e6030123afc608fd294861bf | b7636742fc9ea937e6030123afc608fd294861bf | 2026-10-22T05:41:15Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 交付调度
- 调度状态:未入队;调度记录:无
- entry_id:无;attempt_id:无;窗口执行者:无
- 固定候选:无(base/head、组件与相关配置引用实际尝试)
- 调度依据:policy/delivery.md#合入调度
- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合
## 结果与验证
- 结果:
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
- 影响与剩余项:
## 规划进展与下一步
- 本次推进/剩余:待核对(依据实际成果更新适用方案/规划)
- 首选下一任务:待核对(无适用规划、已完成或受阻时如实说明,不虚构任务)
- 推荐依据:待填写
- 前置条件/待确认:待核对
- 预期产物/验收条件:待定义
- 重复任务核对:待核对(已有/在途任务、PR 与规划记录)
- 启动授权:未授权(建议不自动启动新任务,仍须明确授权)
## 合入与清理
- CI 记录:首轮 CI Checkout 失败——backend 合并 tip a75783f 未推送远端;已补推,本提交重触发 CI。
- CI 记录:88f03f50/67ffd7c9 两轮 CI——首轮 Checkout 失败(backend tip a75783f 未推送,已补推);次轮 test-pg 的 TestMaterialAckAttachmentOnlyDoesNotCallParentModel 超时:产品代码从不设置 Workspace.Writable(M5 预检不可能触发),本地同提交复现 PASS(0.051s,真实 PG/Redis,verify-materialack.json),判定为并行功能新测试的 CI 环境超时 flake;后续提交重触发 CI。
- Review 记录:独立 CLI 评审单元 audit-followups-independent-review(收据 /tmp/agent-stack-b2/reviewf-run.json);结论 APPROVE;报告 .task/reviews/2026-10-22-audit-followups-independent-review.md;被审对象合并基线 vs main
- 合并基线:分支已合并 origin/main(含并行 backend 角色策略/scrarch-session 工作与 frontend/ui pin 前进);modules/backend 组合 pin = a75783f(含 7bcbbdb 与 M5 嵌套指针)。
- 已知既有失败:make test-governance 中 test_svc.SvcDbRecoveryTests.test_addr_change_keeps_database_and_auth 在干净 main(b7636742)同样失败(环境依赖),与本分支无关;CI 环境为最终裁决。其余组合目标(boundaries/pins/libs/core/backend/frontend/ui/agentcmd)全部 PASS(verify-followups-rest.json exit=0)
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-22T05:40:13.195742+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "b7636742fc9ea937e6030123afc608fd294861bf",
"after_head": "b7636742fc9ea937e6030123afc608fd294861bf",
"branch": "main",
"target_branch": "main",
"target_head": "b7636742fc9ea937e6030123afc608fd294861bf",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=b7636742fc",
"[remote] origin/main=b7636742fc ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=5",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] b7636742fc(完整:b7636742fc9ea937e6030123afc608fd294861bf)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "750b4668efa152b0a18152e950ab40051a737f940cf5e108e6adc79324dea82a",
"receipt_sha256": "e2cd793c34846aafbd4c3dd5a77d8bc08fb51323ded1082b212c91e78074bad7",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/e2cd793c34846aafbd4c3dd5a77d8bc08fb51323ded1082b212c91e78074bad7.json"
}
```
## 闭合记录(合入前折叠)
- 本卡在 PR #695(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/695)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
@@ -1,108 +0,0 @@
# feature/rdm-two-trees-followup-c-w8-w10 任务卡
任务主记录:PR #693 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/693;状态:已完成(合入前闭合);owner:weichen(W8a+W8b+W9+W10 全部交付;独立 Review 完成且 MAJOR 全修复;MINOR 留档评审报告;下一步阶段 6 评审包→用户人审);owner:weichen
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= f23f5bb01a8b96ab90686d3a1b07791d7f473de2
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-rdm-two-trees-followup-c-w8-w10(worktree);分支:feature/rdm-two-trees-followup-c-w8-w10
- 分支类型:child;initiative_id:feature-rdm-two-trees-followup;root_target:main;immediate_target:feature/rdm-two-trees-followup;accepting_requirements:false;parent_branch:feature/rdm-two-trees-followup;parent_head_sha:dcca5112923790506e0728a89ae3752349405653;parent_pr:无;parent_source:origin
- 需求分流:verdict=JOIN;selected_initiative=feature/rdm-two-trees-followup;requirement_sha256=f4a1ab636c59ce3df302201310d359eac88b105e8ffecd86da8f7266bf3fa58e;triage_receipt=/tmp/triage-incC.json;receipt_sha256=3fb1f7cb6dd41fc250c764d08dd612749f150f3e619b54cbc3a9144b37a4d298
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
<预期结果、受影响入口/模块/契约;明确不做什么。>
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| w8a-operation-audit-core | W8 领域核:操作审计事实/同事务写入/失败尝试/幂等回执/延迟补全/重放一致 | implementation | cursor | 1 | cursor(auto) | completed | 6fd0478(refs/agent-exec/…,收据 ~/.cache/agent-system-dev/agent-exec-receipts/w8a-operation-audit-core.json) | never | ~/.cache/agent-system-dev/agent-exec-receipts/w8a-operation-audit-core.json |
| w8a-operation-audit-core | W8 领域核(attempt 1 详见 ~/.cache/…/w8a-operation-audit-core.json;transport 6fd0478) | implementation | cursor | 1 | cursor(auto) | completed | 6fd0478 | never | ~/.cache/agent-system-dev/agent-exec-receipts/w8a-operation-audit-core.json |
| w8b-audit-projection | W8 可读投影:DTO 词典/游标分页/可见性接线/rdm.audit.operations(attempt 1 infra 重试) | implementation | cursor | 2 | cursor(auto) | completed | c3d3111 | never | /home/weichen/.cache/agent-system-dev/agent-exec-receipts/w8b-audit-projection.json |
| w9-rdm-audit-adapter | W9 RDM 消费端:audit.read 回调适配 Backend X2(受信裁剪/当时名称/owner 分离/拒绝事实)+ pin 前进 92a41183(worker 自纠 41 位笔误:按前缀 fetch 真 tip) | implementation | cursor | 1 | cursor(auto) | completed | c4c6df7 | never | /home/weichen/.cache/agent-system-dev/agent-exec-receipts/w9-rdm-audit-adapter.json |
| w10-audit-workspace | W10 正式 Frontend:审计工作台/树懒加载/工作卡/依据条/四态/seq(FE 子分支 feature/rdm-two-trees-followup-w10 @fa08be4;知识多挂载显示留 TODO 待 RDM DTO 增补) | implementation | cursor | 2(attempt1 合理扩围重派) | cursor(auto) | completed | fa08be4 | never | /home/weichen/.cache/agent-system-dev/agent-exec-receipts/w10-audit-workspace.json |
| rev-increment-c-rdm | RDM 增量 C 独立评审(0 BLOCKING/5 MAJOR/15 MINOR;报告 docs/reviews/2026-10-22-increment-c-rdm-review.md @b7b5227) | review | pi | 1 | pi(default) | completed | b7b5227 | never | ~/.cache/agent-system-dev/agent-exec-receipts/rev-c-rdm-receipt.json |
| fix-increment-c-rdm-majors | RDM 5 MAJOR 修复(schema-2 目录/copyState/生产写接线等,含回归测试) | repair | cursor | 1 | cursor(auto) | completed | d835ae6 | never | ~/.cache/agent-system-dev/agent-exec-receipts/fix-increment-c-rdm.json |
| rev-increment-c-fe | FE W10 独立评审(0 BLOCKING/3 MAJOR/MINOR;报告 docs/reviews/2026-10-22-increment-c-fe-review.md @a61eaac) | review | pi | 1 | pi(default) | completed | a61eaac | never | ~/.cache/agent-system-dev/agent-exec-receipts/rev-c-fe-receipt.json |
| fix-increment-c-fe-majors | FE 3 MAJOR 修复(openInbox 定位/能力门禁/@ui Collapsible,+6 回归测试) | repair | cursor | 1 | cursor(auto) | completed | 4882e0a | never | ~/.cache/agent-system-dev/agent-exec-receipts/fix-increment-c-fe.json |
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | f23f5bb01a8b96ab90686d3a1b07791d7f473de2 | dcca5112923790506e0728a89ae3752349405653 | 2026-10-22T01:24:37Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 结果与验证
- 结果:
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
- 影响与剩余项:
## 合入与清理
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-22T01:23:50.112388+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "f23f5bb01a8b96ab90686d3a1b07791d7f473de2",
"after_head": "f23f5bb01a8b96ab90686d3a1b07791d7f473de2",
"branch": "main",
"target_branch": "main",
"target_head": "f23f5bb01a8b96ab90686d3a1b07791d7f473de2",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=f23f5bb01a",
"[remote] origin/main=f23f5bb01a ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=5",
"[recycle] merged un-recycled branches: fix/ci-rdm-pin-materialization fix/pal-component-delivery-pins",
"[cards] 已合入未闭合任务卡:fix-pal-component-delivery-pins.md——make task-close 回填归档(主检出运行)",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] f23f5bb01a(完整:f23f5bb01a8b96ab90686d3a1b07791d7f473de2)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "013e1d4d93548576815e9e2fe8e735d049fea47507ef4492478b55e4cf3c7cd6",
"receipt_sha256": "643c76ed799ad8ca4ea16ba47a3510694218cdc18e387586ed9e36d6f9c44502",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/643c76ed799ad8ca4ea16ba47a3510694218cdc18e387586ed9e36d6f9c44502.json"
}
```
## 闭合记录(合入前折叠)
- 本卡在 PR #693(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/693)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
@@ -1,101 +0,0 @@
# feature/rdm-two-trees-followup-w3-rest 任务卡
任务主记录:PR #641 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/641;状态:已完成(合入前闭合);owner:weichen
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 2b7fdc39a1f44163f4766701429d61ffd6926f1a
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-rdm-two-trees-followup-w3-rest(worktree);分支:feature/rdm-two-trees-followup-w3-rest
- 分支类型:child;initiative_id:feature-rdm-two-trees-followup;root_target:main;immediate_target:feature/rdm-two-trees-followup;accepting_requirements:false;parent_branch:feature/rdm-two-trees-followup;parent_head_sha:9e6d9690f64a1e970211b3a092084862a2174ad3;parent_pr:无;parent_source:origin
- 需求分流:免(父 initiative 子任务登记已列出 feature/rdm-two-trees-followup-w3-rest;parent_head_sha=9e6d9690f64a1e970211b3a092084862a2174ad3)
- 授权与交付终点:用户 2026-10-19“授权启动 W3”;终点为 RDM 草稿 PR + 本子任务 PR CI 通过 + 阶段 6 评审包,等待用户阶段 6 人审后才合入父 initiative 分支。未授权:运行库迁移、部署、真实模型/付费请求。用户 2026-10-20 阶段 6 结论“通过”,同时授权 RDM #451 合入 RDM main(附条件:W4 开放结构写入口前必须先补节点种类放置检查)。
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
- 目标:补齐 A1 差距核对中的 W3 余项——图形不透明块、重复表格精确覆盖、节点移动/改名保持引用、旧库备份迁移两次同值、已批准版与旧下载可回取;Dev 侧删除无效 `SVC_RCFWEB_ADDR`。
- 受影响:RDM `internal/domain`(模板块校验、递归节点、两种新事件)及测试;Dev `tests/test_svc_realcombo.py`。
- 非目标:不提供结构变更的 HTTP/插件入口(W4 负责结构提案门控);不改 DHF 渲染;不宣称正式 DHF 模板可用。
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | 2b7fdc39a1f44163f4766701429d61ffd6926f1a | 9e6d9690f64a1e970211b3a092084862a2174ad3 | 2026-10-19T16:15:03Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 结果与验证
- 结果:W3 余项在 RDM 分支 `feature/rdm-two-trees-followup-w3-rest`(`013b8b1`、`96d53f6`、`f3fa0c0`,草稿 PR [#451](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-RDM/pulls/451))与本分支 `80ac3fa` 交付;阶段 6 评审包 docs/superpowers/specs/2026-10-19-rdm-two-trees-followup-w3-review.md。
- 验证:RDM 目标测试 20 项与 `./internal/domain/... ./internal/rdmapp` 全量 PASS(SELF,@f3fa0c0);CI(GATE)见下方 CI 记录;Dev `py_compile` PASS(SELF)。
- 影响与剩余项:仅 RDM 领域原语与测试(无新写入口)及 Dev 测试一行;剩余见评审包第 3 节与 Review 非阻塞项(W4 写入口前补节点放置检查)。
## 合入与清理
- Review 记录:全新会话 code-reviewer(只接收需求、base/head、diff),RDM `5a27d78..f3fa0c0`:PASS-with-fixes,无阻塞项;4 条非阻塞项记入评审包第 2 节(放置检查列为 W4 写入口前置条件)。
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录(RDM):用户 2026-10-20 阶段 6 通过并授权;RDM PR [#451](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-RDM/pulls/451) 核验 HEAD `f3fa0c0e5`、commit status success(2 项,GATE)后合入 RDM main,合入提交 `76b4c0572622c81316511de174469f5f67d31785`;合入后 tree `4cfacf16` 与合入前 `git merge-tree` 预期一致。Dev gitlink 改钉留待 initiative 收口统一组合验证。
- 合入记录(本 PR):NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:RDM worktree `Agent-System-RDM-wt-feature-rdm-two-trees-followup-w3-rest` 已核验无写者、子模块干净(仅删除可再生的 `internal/rdmapp/data` 测试产物)后移除,本地分支已删;Dev 子 worktree 待本 PR 合入后回收。
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-19T16:13:50.819321+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "2b7fdc39a1f44163f4766701429d61ffd6926f1a",
"after_head": "2b7fdc39a1f44163f4766701429d61ffd6926f1a",
"branch": "main",
"target_branch": "main",
"target_head": "2b7fdc39a1f44163f4766701429d61ffd6926f1a",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=2b7fdc39a1",
"[remote] origin/main=2b7fdc39a1 ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=4",
"[recycle] merged un-recycled branches: feature/layered-python-env",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] 2b7fdc39a1(完整:2b7fdc39a1f44163f4766701429d61ffd6926f1a)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "ad2f421c16acf4d72ad5396eb19b97aa168900e85db78022bd77c6eac29b3040",
"receipt_sha256": "abc4088cf722985bd2eeeffeae46cdd899a23e6b1c2711b8849f14f83c8e46bf",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/abc4088cf722985bd2eeeffeae46cdd899a23e6b1c2711b8849f14f83c8e46bf.json"
}
```
## 闭合记录(合入前折叠)
- 本卡在 PR #641(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/641)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
@@ -1,124 +0,0 @@
# feature/rdm-two-trees-followup-w5-w7 任务卡
任务主记录:PR #666 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/666;状态:已完成(合入前闭合);owner:weichen
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= ed3847c85ce111a74f0cb100908abfb40f62db07
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-rdm-two-trees-followup-w5-w7(worktree);分支:feature/rdm-two-trees-followup-w5-w7
- 分支类型:child;initiative_id:feature-rdm-two-trees-followup;root_target:main;immediate_target:feature/rdm-two-trees-followup;accepting_requirements:false;parent_branch:feature/rdm-two-trees-followup;parent_head_sha:13b9c9daddfb78717438777e955dd296ca56439d;parent_pr:无;parent_source:origin
- 需求分流:verdict=JOIN;selected_initiative=feature/rdm-two-trees-followup;requirement_sha256=343b26bddcfc64e013a068fc4f294a28f94c28702c08bafa0f6d92262289e90b;triage_receipt=/tmp/triage-w5w7.json;receipt_sha256=c3948ecfa318defbdd96b5d465665abef97abfcc90472a0a1d216af4dc9ae0dc
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
<预期结果、受影响入口/模块/契约;明确不做什么。>
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| w5a-tracenet-domain-core | internal/domain 追溯网领域核心(revision锁定/候选确认/升版待复核/传递影响+水位/快照/重放) | implementation | cursor | 1 | cursor(auto) | completed | a7a6df0(refs/agent-exec/2533912c/0c0654db) | never | /home/weichen/.cache/agent-system-dev/agent-exec-receipts/w5a-tracenet-domain-core.json |
| w5b-trace-impact-endpoints | rdmapp 影响分析/重算/快照读取/覆盖缺项/pending-recheck 命令面(impact.go + plugin 注册) | implementation | cursor | 1 | cursor(auto) | completed | 2e5fcb7(refs/agent-exec/d98a00bc/23b5e5d0) | never | /home/weichen/.cache/agent-system-dev/agent-exec-receipts/w5b-trace-impact-endpoints.json |
| w7a-project-context-domain | W7 纯领域部分:ProjectContextBundle 组装/失效语义/渐进证据引用(零基座消费,tag 前约束达成) | implementation | cursor | 1 | cursor(auto) | completed | 8327603(refs/agent-exec/a3eba579/153246be) | never | /home/weichen/.cache/agent-system-dev/agent-exec-receipts/w7a-project-context-domain.json |
| w6-x1-recheck-completion | W6 恢复:用户批准修订后,对照清单审计并补齐——消费点全覆盖/入模门禁/存储边界/负例矩阵(Backend 隔离分支,含候选合并 main b4399e3) | implementation | cursor | 1 | cursor(auto) | completed | 8c410b9(refs/agent-exec/412eff9b/0636a7e1) | never | /home/weichen/.cache/agent-system-dev/agent-exec-receipts/w6-x1-recheck-completion.json |
| rev-increment-b-rdm | RDM 增量独立评审(0 BLOCKING/6 MAJOR/14 MINOR) | review | pi | 1 | pi(default) | completed | 56b23a0 | never | ~/.cache/agent-system-dev/agent-exec-receipts/rev-rdm-receipt.json |
| w5-majors-repair | 评审 MAJOR M1–M6 修复(copyState/快照ID/端点校验/生产写入/空分析/别名) | repair | cursor | 1 | cursor(auto) | completed | 04991ed | never | ~/.cache/agent-system-dev/agent-exec-receipts/w5-fix-receipt.json |
| rev-w6-x1-recheck | Backend W6 独立评审(7/7 PASS,0 BLOCKING 0 MAJOR/6 MINOR) | review | pi | 1 | pi(default) | completed | 65122b8 | never | ~/.cache/agent-system-dev/agent-exec-receipts/rev-be-receipt.json |
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | ed3847c85ce111a74f0cb100908abfb40f62db07 | 13b9c9daddfb78717438777e955dd296ca56439d | 2026-10-20T14:48:00Z |
| W5a 后(RDM 侧) | ed3a0f9(RDM main) | a7a6df0(RDM 子分支) | 2026-10-20 | SELF:gofmt/vet ok,domain+rdmapp 全部 ok(81.9s) |
| W5b 后(RDM 侧) | a7a6df0 | 2e5fcb7 | 2026-10-20 | SELF:vet ok;rdmapp(81.2s)+plugins/rdm(3.3s) 全部 ok;gofmt 仅既有 iam_integration_test.go 告警(未触碰) |
| W7a 后(RDM 侧) | 2e5fcb7 | 8327603 | 2026-10-20 | SELF:vet ok;rdmapp 全部 ok(91.5s);grep 证实零 pkg/extension 消费 |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 结果与验证
- 结果:
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
- 影响与剩余项:
## 合入与清理
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-20T14:46:07.178211+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "ed3847c85ce111a74f0cb100908abfb40f62db07",
"after_head": "ed3847c85ce111a74f0cb100908abfb40f62db07",
"branch": "main",
"target_branch": "main",
"target_head": "ed3847c85ce111a74f0cb100908abfb40f62db07",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=ed3847c85c",
"[remote] origin/main=ed3847c85c ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=5",
"[recycle] merged un-recycled branches: feature/fast-slow-o2-a01-checkpoint-design",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] ed3847c85c(完整:ed3847c85ce111a74f0cb100908abfb40f62db07)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "f03caadb3c0597418604c953038e5658758c9546150ca3d711e853ebc0c24a8c",
"receipt_sha256": "657414206727892ce0ed5ce0ee067e220e11e9da50b3b1fae1bd9db4bc530a6e",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/657414206727892ce0ed5ce0ee067e220e11e9da50b3b1fae1bd9db4bc530a6e.json"
}
```
## 阶段 6 结论(2026-10-20)
用户结论「通过」。裁定明细:W5/W6/W7a 交付通过;X1 公开 tag 暂未发布(W7 provider 接线保持门禁,双钥匙另行动作);RDM 评审 MINOR 14 条按包建议列为增量 C 顺手项。合入执行:RDM PR #458 已核 CI(build-and-test+governance success @22b4fb0)后合入 RDM main(93badd7e);Backend PR #231 已核 CI(vet-and-test+governance success @5a4edf3,draft 转正式后)合入 backend main(7a41f22e)。
## 阶段 6 评审包(2026-10-20)
评审包:`docs/superpowers/specs/2026-10-20-rdm-two-trees-followup-w5-w7-review.md`。独立 Review 全部完成(RDM 0 BLOCKING、6 MAJOR 已修复;Backend 7/7 PASS、0 BLOCKING 0 MAJOR),增量 B 达到用户人审就绪点。
## W6 恢复记录(2026-10-20)
用户批准修订(「按已实际纳入来源逐项重新授权」X1 合同 + 去原文迁移边界)。恢复动作:候选 6ba53e4 建分支 feature/w6-x1-source-recheck,合并最新 main(自动合并无冲突,b4399e3),既有多数项已在候选实现(含此前 RED 的无正文测试已绿);派发 w6-x1-recheck-completion 单元补齐消费点/门禁/负例(7 文件,transport 8c410b9),SELF 全绿(build/vet/store/runmgr/workflow/extension)。X1 公开 tag 发布仍为用户双钥匙。
## W6 门禁记录(2026-10-20)
W6 按设计修订文档 `rdm-two-trees-stage6-w6-source-recheck-design-adjustment-r1.md`(状态:**待人工评审**)暂停实现:候选分支 HEAD 6ba53e4(未推送)已有部分实现但含隐私敏感的存储/迁移取舍,须经用户批准该修订后方可继续。本单元按计划 W7 但书仅交付「纯领域摘要与测试」,未消费基座接口,不宣布自动注入完成。
## 闭合记录(合入前折叠)
- 本卡在 PR #666(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/666)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
@@ -0,0 +1,300 @@
# feature/scratch-session-general-agent 任务卡
任务主记录:PR #691 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/691;状态:已完成(合入前闭合);owner:weichen
## 基线与授权
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= a4311c6341c318d4bbf1563b3973358326086794
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-scratch-session-general-agent(worktree);分支:feature/scratch-session-general-agent
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=617539e611dec18b73e0f60c7581ac55246864a96411c38c56fb31dcf561d276;triage_receipt=/tmp/scratch-triage.json;receipt_sha256=1d6484b995d8c51764e91dfeb6ae605e2e3f5ccbf19e07c016f58abe5502d953
- 授权与交付终点:按已批准设计实现无项目对话(scratch);Backend/Frontend/Dev pin 经 PR 合入;未授权:改写历史、分支保护、发布版本、配额/升级成真项目
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 人工决策点:仅限目标/范围/验收变更、重大取舍、高后果操作或无法自动判定的业务验收
- 当前阶段:需求拆解;当前增量:I1(待定义)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
- 目标:所有启用账号可创建彼此隔离的 scratch 会话;侧栏「无项目对话」;固有能力不可配置;拒绝知识树/文档树等项目专属能力。
- 非目标:升级成真项目、协作分享、配额/TTL、nullable project_id。
- 规格:docs/superpowers/specs/2026-10-22-scratch-session-general-agent-design.md
- 计划:docs/superpowers/plans/2026-10-22-scratch-session-general-agent-plan.md
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| backend-scratch-session-create-u1 | Backend internal/store/{scratch,projects}.go、internal/server/{server,project_lifecycle,scratch_session}.go + tests | Backend AGENT-EXECUTION.md digest 22fcb8ac | implementation | 按配置 | 1(fresh_session) | pi MiniMax-M3 | success | apply 16538f1..096e96e → 规范提交 3b4a7af(tree 不变) | never | /tmp/scratch-session-20261022/u1-run.json |
| backend-scratch-session-gates-u2 | Backend store/server/runmgr/app 门禁 + tests | 同上 | implementation | 按配置 | 1(fresh_session) | pi MiniMax-M3 | success | apply 3b4a7af..7bd3e51 → 规范提交 613c068(tree 不变) | never | /tmp/scratch-session-20261022/u2-run.json |
| frontend-scratch-session-sidebar-u3 | Frontend src/{contracts,api,store}.ts、TaskSidebar.tsx、tests | Frontend AGENT-EXECUTION.md | implementation | 按配置 | 1 | cursor | failed-unknown(CLI HTTP/2 keepalive 超时);对账:仅只读命令与 npm ci 下载、隔离区零跟踪改动 → 全新重派 | 无 | never | /tmp/scratch-session-20261022/u3-run.json |
| 同上(重派 2) | 同上 | 同上 | implementation | 按配置 | 1 | cursor | failed-unknown(无有效终态记录;worker 报 src/store.test.ts 旧规则测试在范围外);对账:改动仅在隔离区;扩围 src/store.test.ts(已批准设计内的旧规则测试)→ 单元 u3b | 无 | never | /tmp/scratch-session-20261022/u3-run2.json |
| frontend-scratch-session-sidebar-u3b | 同上 + src/store.test.ts | 同上 | implementation | 按配置 | 1 | cursor | failed-unknown(CLI Connection stalled,验证已通过、终态前断线);对账:只读/npm ci/vitest/tsc、/tmp 临时配置已删 → 全新重派 | 无 | never | /tmp/scratch-session-20261022/u3-run3.json |
| backend-scratch-flag-u2b | Backend server 会话响应 scratch 标记 + store.ScratchProjectIDs + tests | Backend AGENT-EXECUTION.md | implementation | 按配置 | 1(fresh_session) | pi | success | apply → 规范提交 f3f70a7(tree 不变) | never | /tmp/scratch-session-20261022/u2b-run.json |
| 同上 u3b(重派 4) | 同上 | 同上 | implementation | 按配置 | 1 | cursor | failed-unknown(最终消息含两份有效终态记录:后台 npm ci 完成通知后重复输出);对账:改动仅在隔离区,外部仅 npm ci 下载 → 单元 u3c 增加“前台执行、仅一份终态记录”约束后全新重派 | 无 | never | /tmp/scratch-session-20261022/u3-run4.json |
| frontend-scratch-session-sidebar-u3c | 同 u3b | 同上 | implementation | 按配置 | 1(fresh_session) | cursor | success | apply 18fc958..62b7442 → 规范提交 1d7f2a3(tree 不变) | never | /tmp/scratch-session-20261022/u3-run5.json |
| backend-scratch-context-and-bus-gates-u2c | Backend ProjectContext 门禁 + Bus.SetHostProjectGuard + scratch fail-closed + tests | Backend AGENT-EXECUTION.md | implementation | 按配置 | 1(fresh_session) | pi | success | apply → 规范提交 2fc6e7e(tree 不变) | never | /tmp/scratch-session-20261022/u2c-run.json |
| backend-scratch-failclosed-both-ways-u2d | Backend confirmedScratchProject 双向 fail-closed + tests | 同上 | implementation | 按配置 | 1(fresh_session) | pi | success | apply → 规范提交 b704ba9(tree 不变) | never | /tmp/scratch-session-20261022/u2d-run.json |
| frontend-scratch-hide-project-surfaces-u3d | Frontend Conversation/Workbench/ConversationWork 隐藏项目专属面 | Frontend AGENT-EXECUTION.md | implementation | 按配置 | 1 | cursor | scope-expansion(UI ActivityCardView 必须检查 callbacks.onArchive);人工授权扩围到 UI 仓 | 无 | never | /tmp/scratch-session-20261022/u3d-run.json |
| frontend-scratch-hide-project-surfaces-u3e | 同 u3d(UI 侧另单元处理;本单元只断言回调) | 同上 | implementation | 按配置 | 1(fresh_session) | cursor | success | apply → 规范提交 b02ff83(tree 不变) | never | /tmp/scratch-session-20261022/u3e-run.json |
| ui-activity-card-archive-optional-u5 | UI ActivityCardView 仅在有 onArchive 时渲染留档按钮 + tests | UI AGENT-EXECUTION.md | implementation | 按配置 | 1(fresh_session) | cursor | success | apply → 规范提交 db5129c(tree 不变) | never | /tmp/scratch-session-20261022/u5-run.json |
## 关联方向与规划
- 适用状态:待核对(无适用规划时说明原因)
- 主记录路径/版本:待核对(路径按仓库根目录定位,归档后不变;只引用唯一正文)
- 对应里程碑:待核对
## 需求分析
| ID | 描述 | 项目符合度及依据 | 优先级 | 验收条件 | 依赖 | 结论 |
|---|---|---|---|---|---|---|
| R1 | 待填写 | 待评估 | 待定 | 待定义 | 待确认 | 待评审 |
## 方案与计划
- 方案引用:待填写(含对象版本)
- 实施计划引用:待填写(含增量与需求 ID)
- 测试计划引用:待填写(含需求 ID 与验收条件)
## 阶段记录
| 阶段 | 对象版本 | 状态 | 决策/执行者 | 时间 | 依据 |
|---|---|---|---|---|---|
| 需求拆解 | 待定 | 待执行 | — | — | — |
| 需求评估 | 待定 | 待执行 | — | — | — |
| 需求表确认 | 待定 | 待执行 | — | — | — |
| 方案设计 | 待定 | 待执行 | — | — | — |
| 实施计划 | 待定 | 待执行 | — | — | — |
| 实施与自检 | 待定 | 待执行 | — | — | — |
| 正式测试与验收 | 待定 | 待执行 | — | — | — |
| 代码 Review | 待定 | 待执行 | — | — | — |
| 合入 | 待定 | 待执行 | — | — | — |
| 合入核验与收口 | 待定 | 待执行 | — | — | — |
## 探索试验与迭代
| 增量/试验 | 假设 | 范围 | 判断和停止条件 | 授权/决策依据 | 结果 | 变更影响 | 需重审补测 |
|---|---|---|---|---|---|---|---|
| 待定义 | — | — | — | 待核对 | NOT_RUN | 待评估 | 待评估 |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | a4311c6341c318d4bbf1563b3973358326086794 | a4311c6341c318d4bbf1563b3973358326086794 | 2026-10-22T01:43:32Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 交付调度
- 调度状态:未入队;调度记录:无
- entry_id:无;attempt_id:无;窗口执行者:无
- 固定候选:无(base/head、组件与相关配置引用实际尝试)
- 调度依据:policy/delivery.md#合入调度
- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合
## 结果与验证
- 结果:
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
- 影响与剩余项:
## 规划进展与下一步
- 本次推进/剩余:待核对(依据实际成果更新适用方案/规划)
- 首选下一任务:待核对(无适用规划、已完成或受阻时如实说明,不虚构任务)
- 推荐依据:待填写
- 前置条件/待确认:待核对
- 预期产物/验收条件:待定义
- 重复任务核对:待核对(已有/在途任务、PR 与规划记录)
- 启动授权:未授权(建议不自动启动新任务,仍须明确授权)
## 合入与清理
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-22T01:41:26.489856+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "a4311c6341c318d4bbf1563b3973358326086794",
"after_head": "a4311c6341c318d4bbf1563b3973358326086794",
"branch": "main",
"target_branch": "main",
"target_head": "a4311c6341c318d4bbf1563b3973358326086794",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=a4311c6341",
"[remote] origin/main=a4311c6341 ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=6",
"[recycle] merged un-recycled branches: feature/card-close-20261022-012859Z fix/ci-rdm-pin-materialization fix/pal-component-delivery-pins",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] a4311c6341(完整:a4311c6341c318d4bbf1563b3973358326086794)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "5dc418ef2935ca343c4d18b9a8b9408c31d0870061bf48a0cd20d1efeb574159",
"receipt_sha256": "59b8a99e07c11121e009221f6700874c9650c4f057f494394d05d5e47eec3f95",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/59b8a99e07c11121e009221f6700874c9650c4f057f494394d05d5e47eec3f95.json"
}
```
| 同步(记录前对象) | 1de7c28c2122609dd9b6435881696492bd052a34 | 635459316dbd4e4a5134f4e409bed72a5dd02730 | 2026-10-22T05:18:42Z |
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-22T05:18:47.691228+00:00",
"command": "make test",
"exit_code": 2,
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-scratch-session-general-agent",
"before_head": "36a1d4084e8bb7aaea6f04e29fb4d3492f4f0db3",
"after_head": "36a1d4084e8bb7aaea6f04e29fb4d3492f4f0db3",
"branch": "feature/scratch-session-general-agent",
"target_branch": "main",
"target_head": "1de7c28c2122609dd9b6435881696492bd052a34",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"warn: deps/agent-native deps/development-manager is bd3a7de57a3a, Dev is 2c2e37255807",
"ok deps/agent-native error-code-system",
"ok deps/agent-native logging-system",
"ok deps/agent-squad agent-intent",
"ok deps/agent-squad agent-native",
"warn: deps/agent-squad deps/development-guidelines is 2688fd2be6a5, Dev is a15db5085585",
"warn: deps/agent-squad deps/development-manager is bd3a7de57a3a, Dev is 2c2e37255807",
"ok deps/agent-squad error-code-system",
"ok deps/agent-squad logging-system",
"warn: deps/agent-intent deps/development-guidelines is 2688fd2be6a5, Dev is a15db5085585",
"warn: deps/agent-intent deps/development-manager is bd3a7de57a3a, Dev is 2c2e37255807",
"ok deps/agent-intent error-code-system",
"ok deps/agent-intent logging-system",
"make[1]: *** [Makefile:209: test-pins] Error 1",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-scratch-session-general-agent'"
],
"output_sha256": "2d377eb17fd54185095a73449eb657b742e645e180e1d52202cdd168114fe7e7",
"receipt_sha256": "21c7a8c6a8ffc79adb81ed7e90b6c50d4f1aaaa941de12c2a4cd64e8b47e4aa8",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/21c7a8c6a8ffc79adb81ed7e90b6c50d4f1aaaa941de12c2a4cd64e8b47e4aa8.json"
}
```
| 同步(记录前对象) | b7636742fc9ea937e6030123afc608fd294861bf | 1dee480dff7203aff3bd6b57661645fe0b890bce | 2026-10-22T05:49:12Z |
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-22T05:49:16.822650+00:00",
"command": "make test",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-scratch-session-general-agent",
"before_head": "aa77f20184ace3302480642aef5d412819792da8",
"after_head": "aa77f20184ace3302480642aef5d412819792da8",
"branch": "feature/scratch-session-general-agent",
"target_branch": "main",
"target_head": "b7636742fc9ea937e6030123afc608fd294861bf",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "7a447319f8c4fdedb68da2ef8c0fc740d86697df8f8af7650980c77a56befa7b",
"output_tail": [
"added 162 packages in 945ms",
"",
" RUN v4.1.11 /home/weichen/projects/Agent-System-Dev-wt-feature-scratch-session-general-agent/modules/ui",
"",
"",
" Test Files 19 passed (19)",
" Tests 188 passed (188)",
" Start at 05:56:58",
" Duration 1.68s (transform 1.31s, setup 0ms, import 3.38s, tests 3.68s, environment 6.91s)",
"",
"knip unused exports 61 <= 61",
"cd deps/agent-command && go vet ./... && go test -count=1 ./...",
"ok \tagent-command\t0.002s",
"ok \tagent-command/conformance\t0.002s",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-scratch-session-general-agent'"
],
"output_sha256": "4fee33a7ba3812f667660d6c2b49348ae902983cdfebd6a5f3f13bedb4611f0b",
"receipt_sha256": "1331f3cff5d6a225efa50af64c5e1301faee72c380b995ea08b4a1a7beff79da",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/1331f3cff5d6a225efa50af64c5e1301faee72c380b995ea08b4a1a7beff79da.json"
}
```
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-22T06:14:22.167417+00:00",
"command": "make test",
"exit_code": 2,
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-scratch-session-general-agent",
"before_head": "b672792f77f612d563720bff99a55ff5aa721242",
"after_head": "b672792f77f612d563720bff99a55ff5aa721242",
"branch": "feature/scratch-session-general-agent",
"target_branch": "main",
"target_head": "33d6ef69212595c680a744549986c19bb6acd6c3",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
" File \"/home/weichen/projects/Agent-System-Dev-wt-feature-scratch-session-general-agent/tests/test_svc.py\", line 227, in svc",
" self.assertEqual(proc.returncode, 0, proc.stdout)",
" ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
"AssertionError: 1 != 0 : \u001b[1;33m[svc]\u001b[0m [dev] 跳过 manifest 迁移:需 python3 与 scripts/svc_manifest.py",
"\u001b[1;34m[svc]\u001b[0m [dev] 无 pidfile——没有由本脚本启动的实例",
"\u001b[1;31m[svc]\u001b[0m [dev] 端口被占用(非本服务):LISTEN 0 4096 127.0.0.1:18794 0.0.0.0:* users:((\"python3\",pid=1444356,fd=3)) ",
"\u001b[1;31m[svc]\u001b[0m 处理:停掉该进程,或换端口(--port / --addr)",
"",
"",
"----------------------------------------------------------------------",
"Ran 539 tests in 373.148s",
"",
"FAILED (failures=1, skipped=18)",
"make[1]: *** [Makefile:232: test-governance] Error 1",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-scratch-session-general-agent'"
],
"output_sha256": "572e274146ea0cd7b0f87865cf16c8be406ca4eda51bca7ef8469be6812ddec9",
"receipt_sha256": "d2b9cb21e0ab4dcf1d4b03bc939dd9f8bcd94faadff03b19acacad81b5465848",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/d2b9cb21e0ab4dcf1d4b03bc939dd9f8bcd94faadff03b19acacad81b5465848.json"
}
```
| 同步(记录前对象) | 33d6ef69212595c680a744549986c19bb6acd6c3 | 1970dccb7c8c11fb69ad739ae1338e97ca4ac0a6 | 2026-10-22T06:21:57Z |
## 闭合记录(合入前折叠)
- 本卡在 PR #691(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/691)合入前闭合并归档,随该 PR 一同合入。
- 该 PR 的合并提交即本任务的落地记录。
- 合入后无需回填,也无需单独的闭合 PR。
@@ -0,0 +1,99 @@
# fix/svc-test-port-governance 任务卡
任务主记录:PR #694 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/694(merge 37e194871f);状态:已完成(回填);owner:weichen
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 33d6ef69212595c680a744549986c19bb6acd6c3
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-svc-test-port-governance(worktree);分支:fix/svc-test-port-governance
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=bb1fb490ff22f2cc7a7edfe11e0400569bf98c4e879d5527493186aa75429f31;triage_receipt=/tmp/port2-triage2.json;receipt_sha256=a8e49e2792cd069b95e4db5bc7f7d492d164ff84c21ef1001c3bbaf3de7ba364
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
<预期结果、受影响入口/模块/契约;明确不做什么。>
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | 33d6ef69212595c680a744549986c19bb6acd6c3 | 33d6ef69212595c680a744549986c19bb6acd6c3 | 2026-10-22T06:18:16Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 结果与验证
- 结果:
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
- 影响与剩余项:
## 合入与清理
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-22T06:17:27.856549+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "33d6ef69212595c680a744549986c19bb6acd6c3",
"after_head": "33d6ef69212595c680a744549986c19bb6acd6c3",
"branch": "main",
"target_branch": "main",
"target_head": "33d6ef69212595c680a744549986c19bb6acd6c3",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=33d6ef6921",
"[remote] origin/main=33d6ef6921 ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=6",
"[recycle] merged un-recycled branches: fix/rdm-pin-advance-latest",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] 33d6ef6921(完整:33d6ef69212595c680a744549986c19bb6acd6c3)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "bae6a238774330d77e016166c60b79b796a22ec8cbdfed58574a18058fce3679",
"receipt_sha256": "74d9784c1101c6a196fe3571ae41323a102a8d97a92fe15bc3270d00a7a71b7d",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/74d9784c1101c6a196fe3571ae41323a102a8d97a92fe15bc3270d00a7a71b7d.json"
}
```
| 同步(记录前对象) | d65195bd41317e480c5a0cfa5133b0ad98a48d4b | a1e0c85c0400ce300a847da276e70d520f842844 | 2026-10-22T07:17:59Z |
## 闭合记录(card-autoclose 回填)
- 依据:merge 37e194871f4ce8f60c7678b64402bec3541fcbd1(PR #694)已包含于 origin/main,分支成果已落盘;本段为合入后回填。
- 合并主题:Merge pull request 'Use dynamic ports in the two remaining hardcoded test bindings' (#694) from fix/svc-test-port-governance into main
- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。
+178
View File
@@ -0,0 +1,178 @@
# feature/container-storage-plan-u1 任务卡
任务主记录:<PR 链接,交付前填写>;状态:进行中;owner:weichen
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 955a32de2bb49171348bad5f566cce935df19212
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan-u1(worktree);分支:feature/container-storage-plan-u1
- 分支类型:child;initiative_id:feature-container-storage-plan;root_target:main;immediate_target:feature/container-storage-plan;accepting_requirements:false;parent_branch:feature/container-storage-plan;parent_head_sha:b360bcb9745e20d9d4f9f06c44a2936f47217ae4;parent_pr:无;parent_source:origin
- 需求分流:免(父 initiative 子任务登记已列出 feature/container-storage-plan-u1;parent_head_sha=b360bcb9745e20d9d4f9f06c44a2936f47217ae4)
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
- 授权与交付终点:在父 initiative 授权范围内实施方案 U1(环境与数据归属合同),交付终点为合入父分支 feature/container-storage-plan。不含:生产数据盘点/访问、部署、生产迁移、容器改造实施(U2 起)。
- 当前阶段:实施与自检
## 目标与非目标
**目标(方案 U1)**:扩展现有实例配置与 manifest,绑定基座库、RDM 领域库、知识库三个逻辑数据库、持久文件根和环境身份(prod/test/verify);产出全部必需持久数据所有者的归属清单(类型、源版本、目录、引用形式、唯一写者;不含秘密与业务内容)。
**主要落点**:`scripts/svc_manifest.py`、`scripts/svc.sh`、`docs/svc.md`、`PROJECT.md`;新增环境 schema/示例建议置 `deploy/`。测试扩展 `tests/test_svc_identity.py`、`tests/test_svc_lifecycle_isolation.py`、`tests/test_svc_precheck.py`。
**完成条件**:配置、归属清单和迁移范围有唯一解释;明确需迁移的每个数据所有者;旧配置升级保留身份;错库/错根/foreign marker/缺失必需绑定拒绝;同名项目或冲突端口在修改前失败;在另一 checkout 调用仍定位同一资源。
**非目标**:不实现容器启停(U2)、迁移执行(U3)、备份恢复包(U4)、克隆隔离(U5);不改业务权限模型;不引入第二份 DSN/卷权威(KTD3:manifest 唯一权威)。
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| container-storage-u1-env-data-ownership | manifest/svc 绑定三库+文件根+环境身份;数据归属清单 | ccceb3bf… | implementation | cursor,antigravity,pi-glm,pi,codex | 1 | cursor(auto) | failed-unknown | 无(失败保留) | 否 | /tmp/container-storage-u1-run-receipt.json |
| container-storage-u1-env-data-ownership | 同上(采用 attempt1 补丁并验证) | ccceb3bf… | implementation | cursor,… | 2 | cursor(auto) | failed-unknown | 无(JSON 后带尾文解码失败,现场保留) | 否 | /tmp/container-storage-u1-run2-receipt.json |
| container-storage-u1-env-data-ownership | 同上 | ccceb3bf… | implementation | cursor,… | 3 | cursor(auto) | failed-unknown | 无(CLI 启动即报 Cannot use this model: auto,模型列表为空;单测 CLI 正常,判瞬时故障) | 否 | /tmp/container-storage-u1-run3-receipt.json |
| container-storage-u1-env-data-ownership | 同上 | ccceb3bf… | implementation | cursor,antigravity,… | 4 | cursor(auto) | failed-unknown | 无(工作完成且 80 测试通过,但终态 JSON 后再次附带尾文解码失败;cursor 累计 4 次尝试耗尽,转下一候选) | 否 | /tmp/container-storage-u1-run4-receipt.json |
| container-storage-u1-env-data-ownership | — | ccceb3bf… | implementation | (run5 于尝试启动前被 host 终止并删除其私有运行目录,无工作产物;目的:避免 cursor 超上限重试) | 5a | — | cancelled | 无 | 否 | 无(无收据;host 披露于本行) |
| container-storage-routing-promote-antigravity | AGENT-EXECUTION.md implementation 顺序临时调整 | ccceb3bf… | implementation | cursor,… | 1(第 2 次运行;第 1 次遇 cursor 模型列表瞬时为空 failed-unknown) | cursor(auto) | completed | apply(0c8bea37) | 否 | /tmp/container-storage-routing-receipt2.json |
| container-storage-u1-env-data-ownership | 同上(采用 attempt1 补丁内容,经 antigravity 全新会话独立重做并验证) | ccceb3bf… | implementation | antigravity,pi-glm,pi,cursor,codex | 1 | agy(gemini-3.8-flash-high) | completed | apply(2d75d101) | 否 | /tmp/container-storage-u1-run7-receipt.json |
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | 955a32de2bb49171348bad5f566cce935df19212 | b360bcb9745e20d9d4f9f06c44a2936f47217ae4 | 2026-10-22T09:12:44Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 结果与验证(完成后填写)
- 结果:U1 完成。manifest/svc 扩展三库+文件根+环境身份绑定与错误拒绝;deploy/data-ownership.md 归属清单与 env-bindings.example.env 示例;文档同步。经 agent-exec transport ed39db43 进入分支(2d75d101)。
- 验证:三套件 80 项 PASS(1 skip,SKIP 为环境缺 DSN 的既有用例);make test-affected 574 项 PASS(18 skip)。证据见上方两个导入收据(SELF)。容器真实运行验证属 U2,NOT_RUN。
- 影响与剩余项:无阻塞。生产数据盘点需授权(方案 U1 生产部分推迟到 O1 前核实)。
## 合入与清理
- Review 记录:REVIEW PASS(无阻塞项;N1—N8 非阻塞,报告 docs/superpowers/reviews/2026-10-22-container-storage-u1-review.md,transport 7f0f... 见 /tmp/container-storage-u1-review-receipt2.json)。审阅者:pi-glm 全新会话(fresh_session,非实施者);base 955a32de、head e1f08650;重放 80 项单测 OK
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-22T09:11:55.612223+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan",
"before_head": "b360bcb9745e20d9d4f9f06c44a2936f47217ae4",
"after_head": "b360bcb9745e20d9d4f9f06c44a2936f47217ae4",
"branch": "feature/container-storage-plan",
"target_branch": "main",
"target_head": "955a32de2bb49171348bad5f566cce935df19212",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan'",
"[repo] branch=feature/container-storage-plan head=b360bcb974",
"[remote] origin/main=955a32de2b ahead=2 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=6",
"[initiatives] local cards (not authoritative): feature/container-storage-plan",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] 955a32de2b(完整:955a32de2bb49171348bad5f566cce935df19212)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan'"
],
"output_sha256": "58ef898d944057a44fb02e1fabb6acc2fd97943beed355977229d13711b2e6b2",
"receipt_sha256": "59e8b2e7b7032c19207a19f9427486439ca15250333e83ddd162db739c5dc395",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/59e8b2e7b7032c19207a19f9427486439ca15250333e83ddd162db739c5dc395.json"
}
```
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-22T10:41:41.286181+00:00",
"command": "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_svc_identity tests.test_svc_lifecycle_isolation tests.test_svc_precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan-u1",
"before_head": "2d75d101a04ce1d1c9fa1d66d9567894222399d5",
"after_head": "2d75d101a04ce1d1c9fa1d66d9567894222399d5",
"branch": "feature/container-storage-plan-u1",
"target_branch": "main",
"target_head": "98ee59c477785b2d5094fcbea3e67a4dc864c388",
"root_target": "main",
"immediate_target": "feature/container-storage-plan",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"........................s.......................................................",
"----------------------------------------------------------------------",
"Ran 80 tests in 66.437s",
"",
"OK (skipped=1)"
],
"output_sha256": "1bc6f83d7e4c64a628d5f29bad07fc4e8cc2468937c66bda4bd9a5e00fc22587",
"receipt_sha256": "bfe74991277ebe308f667532e9b21af8215f453db124afeab1fc3d2115adf67d",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/bfe74991277ebe308f667532e9b21af8215f453db124afeab1fc3d2115adf67d.json"
}
```
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-22T10:42:53.576009+00:00",
"command": "make test-affected",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan-u1",
"before_head": "2d75d101a04ce1d1c9fa1d66d9567894222399d5",
"after_head": "2d75d101a04ce1d1c9fa1d66d9567894222399d5",
"branch": "feature/container-storage-plan-u1",
"target_branch": "main",
"target_head": "98ee59c477785b2d5094fcbea3e67a4dc864c388",
"root_target": "main",
"immediate_target": "feature/container-storage-plan",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"test_invalid_json_is_denied (test_write_guard.WriteGuardTests.test_invalid_json_is_denied) ... ok",
"test_missing_parent_on_main_is_denied (test_write_guard.WriteGuardTests.test_missing_parent_on_main_is_denied) ... ok",
"test_missing_unknown_and_malformed_write_input_are_denied (test_write_guard.WriteGuardTests.test_missing_unknown_and_malformed_write_input_are_denied) ... ok",
"test_non_git_destination_is_explicitly_outside_scope (test_write_guard.WriteGuardTests.test_non_git_destination_is_explicitly_outside_scope) ... ok",
"test_nonwrite_tools_are_outside_scope (test_write_guard.WriteGuardTests.test_nonwrite_tools_are_outside_scope) ... ok",
"test_relative_path_uses_hook_cwd_not_process_cwd (test_write_guard.WriteGuardTests.test_relative_path_uses_hook_cwd_not_process_cwd) ... ok",
"test_symlink_into_main_with_missing_parents_is_denied (test_write_guard.WriteGuardTests.test_symlink_into_main_with_missing_parents_is_denied) ... ok",
"test_valid_multi_file_patch_on_feature_is_allowed (test_write_guard.WriteGuardTests.test_valid_multi_file_patch_on_feature_is_allowed) ... ok",
"",
"----------------------------------------------------------------------",
"Ran 574 tests in 400.853s",
"",
"OK (skipped=18)",
"make[2]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan-u1'",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan-u1'"
],
"output_sha256": "d738cef7d9ea78bcc4624e0fe8eb87b651e88712fcd283bb26c21755168604e9",
"receipt_sha256": "0565d5b327cb8d01e48deda867bd7369dd5728b5ad9024b51bbd6cb1a2fdf246",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/0565d5b327cb8d01e48deda867bd7369dd5728b5ad9024b51bbd6cb1a2fdf246.json"
}
```
+177
View File
@@ -0,0 +1,177 @@
# feature/container-storage-plan 任务卡
任务主记录:<PR 链接,交付前填写>;状态:进行中;owner:weichen
## 基线与授权
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 955a32de2bb49171348bad5f566cce935df19212
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan(worktree);分支:feature/container-storage-plan
- 分支类型:initiative;initiative_id:feature-container-storage-plan;root_target:main;immediate_target:main;accepting_requirements:true
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=53a3aac8b5638347861577e2ab6f487c182b364e44b1fb0b563df0fdb5834870;triage_receipt=/tmp/container-storage-plan-triage-20261022-b.json;receipt_sha256=31e6accb098e8572e3698e04e3dbafc340fd9ef300df7a09110f1cb249aabb44
- 授权与交付终点:按已审阅方案实施 U1—U6(M1—M3)并交付到 main;含各仓代码/测试/文档变更、PR/CI/合入。不含:生产迁移 O1、生产数据读取/写入、部署到生产、数据库大版本升级。
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 人工决策点:仅限目标/范围/验收变更、重大取舍、高后果操作或无法自动判定的业务验收
- 当前阶段:实施与自检(M1/U1 起);当前增量:I1=U1
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
### 章程(charter)
**用途与目标**:按已审阅方案(`docs/superpowers/plans/2026-10-22-container-storage-plan.md`)把部署形态改造为:一个完整应用服务二进制、两个 PostgreSQL 容器(业务基座库 + RDM 领域库)、知识库 PostgreSQL/pgvector 容器(第三个逻辑库)、外置持久文件根;生产/测试/验证三套环境独立配置、数据、身份与凭据;形成可验证的完整备份恢复与环境克隆隔离流程(需求 R1—R8、决策 KTD1—KTD7 见方案)。
**接纳范围**:方案 Implementation Units U1—U6 及其测试;各仓仅做阻塞目标的最小接线。U1 环境与数据归属合同;U2 容器数据库与 svc 接线;U3 迁移与文件引用兼容;U4 组合备份与恢复新目标;U5 测试/验证克隆与环境选择;U6 单二进制与组合验收、运维交付。
**非目标**:生产迁移执行(方案 O1,另获窗口授权后单独安排);高可用平台、Kubernetes、托管云数据库、全量对象存储、应用微服务化;零停机/实时复制/PITR;删除既有 SQLite/原生 PG 路径;PG 大版本升级。
**分级测试策略**:L2→L1 用 `make test-affected` 最小直接检查;L1→main 最终窗口跑完整组合(`make test`,含 `test-pg`、`svc-realcombo`、`test-regulations-kb`);容器与文件恢复必须有真实临时环境验证,缺 DSN 的跳过不计通过。
**验收条件**:方案 Definition of Done——单二进制在目标环境启动并符合两容器三库边界;三环境独立可启停重建且隔离实测;完整备份恢复到新目录且原文/引用不变;真实迁移 fixture 对账通过;精确组件组合、测试证据、独立 Review、必需 CI 与规范文档齐备。
**追加领域规则**:manifest 是身份与期望态唯一权威,Compose 只作版本化模板(KTD3);普通启停/重建不隐式删卷、不隐式恢复旧备份、不在数据库失败时回退空 SQLite(R7);镜像按选定 digest/扩展版本逐项核对(KTD7);秘密不入仓不入日志(baseline 硬边界 2)。
**生命周期**:M1(U1→U2)→ M2(U3→U4)→ M3(U5→U6)→ M4 生产切换(O1 另行授权,不在本 initiative 内)。U1 完成后按实测数据规模细化后续工期;全部必需子任务合入后置 `accepting_requirements=false` 收口合入 main。
## 子任务登记
| 子任务分支 | 必需 | 状态 | PR |
|---|---|---|---|
| feature/container-storage-plan-u1 | 是 | 未开始 | — |
| feature/container-storage-plan-u2 | 是 | 未开始 | — |
| feature/container-storage-plan-u3 | 是 | 未开始 | — |
| feature/container-storage-plan-u4 | 是 | 未开始 | — |
| feature/container-storage-plan-u5 | 是 | 未开始 | — |
| feature/container-storage-plan-u6 | 是 | 未开始 | — |
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
## 关联方向与规划
- 适用状态:适用(本 initiative 的方案与实施计划)
- 主记录路径/版本:`docs/superpowers/plans/2026-10-22-container-storage-plan.md`(2026-10-22 审阅快照,含落库状态注记)
- 对应里程碑:M1(U1→U2)→ M2(U3→U4)→ M3(U5→U6);M4/O1 生产切换不在本 initiative
## 需求分析
| ID | 描述 | 项目符合度及依据 | 优先级 | 验收条件 | 依赖 | 结论 |
|---|---|---|---|---|---|---|
| R1—R8 | 见方案 Requirements 表(R1 单二进制、R2 容器化、R3 环境绑定、R4 恢复可核对、R5 迁移覆盖、R6 克隆隔离、R7 无隐式破坏、R8 操作记录) | 符合(方案已核对现有 svc/manifest/迁移器/KB Compose 能力) | 高 | 见方案各单元完成条件与 Definition of Done | U1←无;U2←U1;U3←U1/U2;U4←U2/U3;U5←U4;U6←U1—U5 | 纳入本增量(U1—U6) |
## 方案与计划
- 方案引用:`docs/superpowers/plans/2026-10-22-container-storage-plan.md`(2026-10-22 审阅快照;Product Contract、KTD1—KTD7、High-Level Technical Design)
- 实施计划引用:同上 Implementation Units U1—U6 与里程碑 M1—M3(M4/O1 不在本 initiative)
- 测试计划引用:同上 Verification Contract 与各单元测试文件/场景清单
## 阶段记录
| 阶段 | 对象版本 | 状态 | 决策/执行者 | 时间 | 依据 |
|---|---|---|---|---|---|
| 需求拆解 | 方案 Requirements R1—R8 | 已完成 | 主模型(方案会话)+ triage NEW | 2026-10-22 | /tmp/container-storage-plan-triage-20261022-b.json |
| 需求评估 | 方案 Problem Frame/边界假设 | 已完成 | 三视角文档审阅 + 主模型复核 | 2026-10-22 | /tmp/container-storage-plan-20261022/review.md |
| 需求表确认 | R1—R8 纳入 U1—U6 | 已完成 | 主模型 | 2026-10-22 | 方案 Requirements 表 |
| 方案设计 | KTD1—KTD7 | 已完成 | 方案会话(已审阅) | 2026-10-22 | plan.md |
| 实施计划 | U1—U6 + M1—M4 | 已完成 | 方案会话(已审阅) | 2026-10-22 | plan.md Implementation Units |
| 实施与自检 | 待定 | 待执行 | — | — | — |
| 正式测试与验收 | 待定 | 待执行 | — | — | — |
| 代码 Review | 待定 | 待执行 | — | — | — |
| 合入 | 待定 | 待执行 | — | — | — |
| 合入核验与收口 | 待定 | 待执行 | — | — | — |
## 探索试验与迭代
| 增量/试验 | 假设 | 范围 | 判断和停止条件 | 授权/决策依据 | 结果 | 变更影响 | 需重审补测 |
|---|---|---|---|---|---|---|---|
| 待定义 | — | — | — | 待核对 | NOT_RUN | 待评估 | 待评估 |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | 955a32de2bb49171348bad5f566cce935df19212 | 955a32de2bb49171348bad5f566cce935df19212 | 2026-10-22T09:09:21Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 交付调度
- 调度状态:未入队;调度记录:无
- entry_id:无;attempt_id:无;窗口执行者:无
- 固定候选:无(base/head、组件与相关配置引用实际尝试)
- 调度依据:policy/delivery.md#合入调度
- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合
## 结果与验证(完成后填写)
- 结果:
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
- 影响与剩余项:
## 规划进展与下一步
- 本次推进/剩余:待核对(依据实际成果更新适用方案/规划)
- 首选下一任务:待核对(无适用规划、已完成或受阻时如实说明,不虚构任务)
- 推荐依据:待填写
- 前置条件/待确认:待核对
- 预期产物/验收条件:待定义
- 重复任务核对:待核对(已有/在途任务、PR 与规划记录)
- 启动授权:未授权(建议不自动启动新任务,仍须明确授权)
## 合入与清理
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-22T09:08:28.920468+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "955a32de2bb49171348bad5f566cce935df19212",
"after_head": "955a32de2bb49171348bad5f566cce935df19212",
"branch": "main",
"target_branch": "main",
"target_head": "955a32de2bb49171348bad5f566cce935df19212",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=955a32de2b",
"[remote] origin/main=955a32de2b ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=5",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] 955a32de2b(完整:955a32de2bb49171348bad5f566cce935df19212)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "ed0263b348ce265d081aab1faa0d1370f2a9478410fadfc3b9cec8a810c1295f",
"receipt_sha256": "124a47e6bfbb43cbbbc398ad10bf42e3d448373a80affd297ed25c41d8b2b07a",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/124a47e6bfbb43cbbbc398ad10bf42e3d448373a80affd297ed25c41d8b2b07a.json"
}
```
-253
View File
@@ -1,253 +0,0 @@
# feature/rdm-two-trees-followup 任务卡
任务主记录:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/635;状态:进行中;owner:weichen
## 基线与授权
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 46e331a7a81c94c6f5881c997ae0aca45664c830
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-rdm-two-trees-followup(worktree);分支:feature/rdm-two-trees-followup
- 分支类型:initiative;initiative_id:feature-rdm-two-trees-followup;root_target:main;immediate_target:main;accepting_requirements:true
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=ec5195ceec26c05d94600716514c5f306bd3d2aba2a18e4e71f54a660e0b1b83;triage_receipt=/tmp/tt-followup-triage-20261019.json;receipt_sha256=3abe686c520606f4969e17668843666bfafc4cebd7f869995121ab82ed5dd345
- 授权与交付终点:用户 2026-10-19 在遗留项推进中选定“两棵树后续大任务”;本轮终点为建卡登记剩余项,并把第一个增量 A1(W0 清单守护 + svc 测试动态端口 + W3 余项差距核对)推进到阶段 6 评审包。manual 模式:每个增量须用户阶段 6 人审后才合入;未授权:Backend 发版/tag(X3 公开扩展接口,双钥匙由用户执行)、真实模型与付费请求(W11/W12 另提资源方案)、运行库迁移、部署。
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
- 人工决策点:仅限目标/范围/验收变更、重大取舍、高后果操作或无法自动判定的业务验收
- 当前阶段:A1、W3 余项、增量 B(W5/W6/W7a)均通过阶段 6 并合入本分支;下一增量:C(W8/W10,待登记);W7 provider 接线等 X1 tag(用户双钥匙)
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
## 目标与非目标
### 章程(charter)
**用途与目标**:承接 `.task/archive/feature-rdm-two-trees-latest-templates.md`(部分收口,Dev #630)未交付部分,按已批准的阶段 4 方案与阶段 5 计划 r1(`modules/documents/docs/records/2026-10-14-rdm-two-trees-latest-templates/rdm-two-trees-stage5-implementation-test-plan-r1.md` 与 work-observation 增补计划 r1)完成两棵树一网的模板、递归实例、追溯、上下文、审计与正式界面。
**接纳范围**:W0(DHF 清单守护,原分支 9873a55 未合入部分)、svc 测试动态端口修复、W3 余项(旧数据迁移等,见 A1 差距核对)、W5 追溯网、W7 上下文提供方、W8 RDM 审计投影、W10 正式前端、W11 DHF 全量保真、W12 含真实模型全组合验收、W13 收口,以及 X3/R3/F3/V3 产品链。
**非目标**:IAM 项目协作与联动权限(feature-iam-role-permission-linked-capabilities 范围);重新设计已批准方案;未经单独授权的发版、迁移、部署与付费请求。
**分级测试策略**:每增量 SELF 定向测试 + 独立 Review → 用户阶段 6 人审 → PR CI(GATE);阶段 7 只在全目标候选固定后正式验收。
**生命周期**:增量 A1 → 用户阶段 6 → 后续增量按原计划 B(W5–W7)、C(W8–W10)、D(W11/W12)推进;X3 发版与 W11/W12 资源各自单独授权。
## 子任务登记
| 子任务分支 | 必需 | 状态 | PR |
|---|---|---|---|
| A1:W0 守护 + svc 动态端口 + W3 差距核对(本分支内增量) | 是 | 已合入(本分支 000a2d2;用户 2026-10-19 阶段 6 通过) | 本分支 PR #635(草稿,initiative 收口时合入 main) |
| feature/rdm-two-trees-followup-w3-rest | 是 | 已合入(用户 2026-10-20 阶段 6 通过;W3 余项:迁移两次同值、图形不透明块、移动/改名引用、旧批准版回取、重复表格复核;评审包 docs/superpowers/specs/2026-10-19-rdm-two-trees-followup-w3-review.md)。前置条件:W4 开放结构写入口前先补节点种类放置检查;Dev 的 RDM gitlink 改钉在 initiative 收口时统一组合验证 | RDM PR [#451](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-RDM/pulls/451)(合入 RDM main 76b4c05);本子任务 PR [#641](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/641) |
| feature/rdm-two-trees-followup-w5-w7(增量 B:W5/W6/W7a) | 是 | 已合入(用户 2026-10-20 阶段 6 通过:W5 追溯网领域核+命令面、W6 X1 恢复并按批准修订补齐、W7a 纯领域 bundle;独立 Review RDM 0 BLOCKING/6 MAJOR 已修复、Backend 7/7 PASS;评审包 docs/superpowers/specs/2026-10-20-rdm-two-trees-followup-w5-w7-review.md)。W7 provider 接线等 X1 tag(用户双钥匙);Backend MINOR 6 条随接线处理;RDM MINOR 14 条为增量 C 顺手项 | RDM PR [#458](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-RDM/pulls/458)(合入 RDM main 93badd7e);本子任务 PR [#666](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/666);Backend PR [#231](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Backend/pulls/231)(合入 backend main 7a41f22e) |
| B:W5/W7 | 是 | 待登记 | — |
| C:W8/W10 | 是 | 待登记 | — |
| D:W11/W12/W13 | 是 | 待登记(需资源方案) | — |
| X3/R3/F3/V3 | 是 | 待登记(X3 需 Backend 发版,双钥匙) | — |
## Agent 实施收据
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|---|
| two-trees-followup-a1-w0-guard-and-svc-ports | scripts/dhf_catalog_guard.py、tests/test_dhf_catalog_guard.py、tests/test_svc.py、tests/test_svc_recover.py、tests/test_svc_realcombo.py | config ccf5b7c03ce4;unit 6fe9be3e5516 | implementation | cursor → agy → pi | 1(9075eee055af,fresh_session) | cursor | success | apply ff 8cf4619..000a2d2 | 未触发 | /tmp/leftover-20261019/tt-a1-receipt.json(run b7a8e78a2b6d0d8f6803490c7fa8af2e) |
## 关联方向与规划
- 适用状态:适用
- 主记录路径/版本:.task/archive/feature-rdm-two-trees-latest-templates.md;modules/documents/docs/records/2026-10-14-rdm-two-trees-latest-templates/(阶段 4 方案、阶段 5 计划 r1)
- 对应里程碑:两棵树一网与 DHF 模板(阶段 6 增量 A–D)
## 需求分析
| ID | 描述 | 项目符合度及依据 | 优先级 | 验收条件 | 依赖 | 结论 |
|---|---|---|---|---|---|---|
| R1 | W0 DHF 清单守护落地 main | 符合:阶段 5 计划 W0;原实现 38ef548 曾 SELF 通过与限域复审 | P1 | 守护单测通过;不宣称任何模板 published | DHF catalog | 纳入 A1 |
| R2 | svc 测试动态端口 | 符合:并发 worktree 固定端口误入占用路径(cabac58 证据) | P2 | 原断言保留;test_svc/test_svc_recover 通过 | 无 | 纳入 A1 |
| R3 | W3 余项差距核对 | 符合:W3 递归节点主体已在 RDM main(8dd8ebe) | P1 | 逐项列出已交付/缺失与证据 | RDM main | 纳入 A1(仅核对) |
## 方案与计划
- 方案引用:阶段 4 方案 rdm-two-trees-stage4-design-r1.md(已批准)
- 实施计划引用:rdm-two-trees-stage5-implementation-test-plan-r1.md(已批准)W0/W3;A1 评审包见 docs/superpowers/specs/2026-10-19-rdm-two-trees-followup-a1-review.md
- 测试计划引用:同上计划 W0/W3 验证节;A1 定向单测
## 阶段记录
| 阶段 | 对象版本 | 状态 | 决策/执行者 | 时间 | 依据 |
|---|---|---|---|---|---|
| 需求拆解 | R0 46e331a | 完成 | host | 2026-10-19 | 承接部分收口剩余项 |
| 需求评估 | 待定 | 待执行 | — | — | — |
| 需求表确认 | 待定 | 待执行 | — | — | — |
| 方案设计 | 阶段 4 r1 | 沿用已批准方案 | 用户(原任务) | 2026-10-14 | 原卡阶段记录 |
| 实施计划 | 阶段 5 r1 | 沿用已批准计划 | 用户(原任务) | 2026-10-14 | 原卡阶段记录 |
| 实施与自检 | A1 000a2d2 | 完成(SELF) | cursor worker 实施;host 范围审阅与定向单测 | 2026-10-19 | 收据 tt-a1-receipt.json;67 测试 OK(skipped=16 为真实二进制 NOT_RUN) |
| 阶段 6 人审 | A1 679da1a(CI GATE success,test-pg 偶发失败后重跑通过) | 通过 | 用户 | 2026-10-19 | docs/superpowers/specs/2026-10-19-rdm-two-trees-followup-a1-review.md |
| 正式测试与验收 | 待定 | 待执行 | — | — | — |
| 代码 Review | 待定 | 待执行 | — | — | — |
| 合入 | 待定 | 待执行 | — | — | — |
| 合入核验与收口 | 待定 | 待执行 | — | — | — |
## 探索试验与迭代
| 增量/试验 | 假设 | 范围 | 判断和停止条件 | 授权/决策依据 | 结果 | 变更影响 | 需重审补测 |
|---|---|---|---|---|---|---|---|
| A1 | 原 W0/端口修复在最新 main 上可最小适配重新落地 | Dev scripts/tests | 定向单测通过;原断言不删减;不适配则停止并报告 | 用户 2026-10-19 选定 | 守护脚本与单测按 9873a55 原样落地;端口修复按 cabac58 意图在最新 main 重新实现(原补丁不可直接应用),原断言一一改用分配端口未删减;W3 差距见评审包 | 仅测试与守护脚本 | 阶段 6 已通过 |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|---|---|---|---|
| 启动 | 46e331a7a81c94c6f5881c997ae0aca45664c830 | 46e331a7a81c94c6f5881c997ae0aca45664c830 | 2026-10-19T14:38:46Z |
## 证据(Evidence)
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|---|---|---|---|---|---|
## 交付调度
- 调度状态:未入队;调度记录:无
- entry_id:无;attempt_id:无;窗口执行者:无
- 固定候选:无(base/head、组件与相关配置引用实际尝试)
- 调度依据:policy/delivery.md#合入调度
- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合
## 结果与验证(完成后填写)
- 结果:A1 实施完成(000a2d2),评审包已提交,等待用户阶段 6 人审;未合入。
- 验证:定向单测 PASS(SELF,67 测试,skipped=16 为真实二进制 NOT_RUN);PR CI(GATE)见合入与清理节;W3 差距为关键字级 SELF。
- 影响与剩余项:仅 Dev 测试与守护脚本;剩余项见评审包第 3 节(W3 余项、B、C、D、X3 链)。
## 规划进展与下一步
- 本次推进/剩余:A1、W3 余项已验收;剩余 B(W5/W7)、C(W8/W10)、D(W11–W13)、X3 产品链
- 首选下一任务:B(W5 追溯网 / W7 上下文提供方)子任务登记
- 推荐依据:W3 余项已于 2026-10-20 通过阶段 6;B/C 依赖 W3/W4 稳定
- 前置条件/待确认:W4 开放结构写入口前须先补节点种类放置检查(W3 评审非阻塞项 1,用户确认);RDM 文件与 IAM link(RDM iam-link-u2)可能重叠,启动前核对
- 预期产物/验收条件:按阶段 5 计划 W5/W7 验证节
- 重复任务核对:启动前核对
- 启动授权:待用户确定
## 合入与清理
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-19T14:37:49.134350+00:00",
"command": "make precheck",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev",
"before_head": "46e331a7a81c94c6f5881c997ae0aca45664c830",
"after_head": "46e331a7a81c94c6f5881c997ae0aca45664c830",
"branch": "main",
"target_branch": "main",
"target_head": "46e331a7a81c94c6f5881c997ae0aca45664c830",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
"[repo] branch=main head=46e331a7a8",
"[remote] origin/main=46e331a7a8 ahead=0 behind=0",
"[tree] clean submodules_drift=0 uninit=0",
"[worktrees] linked=4",
"[recycle] merged un-recycled branches: feature/layered-python-env",
"[initiatives] local cards (not authoritative): none",
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
"[facts] policy/facts.md 6 条事实均在有效期内",
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
"[R0] 46e331a7a8(完整:46e331a7a81c94c6f5881c997ae0aca45664c830)",
"[worktree] before-sync: recursive component checks passed",
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
],
"output_sha256": "6cba29f361cd5d6ad7c8712594c53dd0899e10d1d89a2b510a8e85522b461cfa",
"receipt_sha256": "17f97ec95374ca86a9e49b4b7976cf318ae1f7ae5052d8a746ab219907e1be4c",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/17f97ec95374ca86a9e49b4b7976cf318ae1f7ae5052d8a746ab219907e1be4c.json"
}
```
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-19T15:01:26.746182+00:00",
"command": "python3 -m unittest tests.test_dhf_catalog_guard tests.test_svc tests.test_svc_recover tests.test_svc_realcombo",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-rdm-two-trees-followup",
"before_head": "000a2d2ae0e29fecd42ff19bf2b42e430be22ef8",
"after_head": "000a2d2ae0e29fecd42ff19bf2b42e430be22ef8",
"branch": "feature/rdm-two-trees-followup",
"target_branch": "main",
"target_head": "2b7fdc39a1f44163f4766701429d61ffd6926f1a",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
".........................[main 579aaee] rdm v2",
" 1 file changed, 1 insertion(+)",
" create mode 100644 marker",
"To /tmp/svc-db-regress-m9jw7ip3/rdmremote.git",
" 526900c..579aaee main -> main",
"..........................ssssssssssssssss",
"----------------------------------------------------------------------",
"Ran 67 tests in 44.079s",
"",
"OK (skipped=16)"
],
"output_sha256": "cdf6732fa953eedac3fcea3f5d042c64a3d16c93a94f76308a212256b05b75e2",
"receipt_sha256": "4642350698dad77b3ab2397ef089219380651672d7f76113650d942b31b2df7a",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/4642350698dad77b3ab2397ef089219380651672d7f76113650d942b31b2df7a.json"
}
```
| 同步(记录前对象) | 2b7fdc39a1f44163f4766701429d61ffd6926f1a | 26e4ea63918b813557022c6bc12c54ff9213e48d | 2026-10-19T15:05:52Z |
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
```json
{
"summary_schema": 1,
"source": "SELF",
"timestamp": "2026-10-19T15:06:00.721625+00:00",
"command": "python3 -m unittest tests.test_dhf_catalog_guard tests.test_svc tests.test_svc_recover tests.test_svc_realcombo",
"exit_code": 0,
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-rdm-two-trees-followup",
"before_head": "dd2f8b1f240a74a0f467533512b88da541c19999",
"after_head": "dd2f8b1f240a74a0f467533512b88da541c19999",
"branch": "feature/rdm-two-trees-followup",
"target_branch": "main",
"target_head": "2b7fdc39a1f44163f4766701429d61ffd6926f1a",
"root_target": "main",
"immediate_target": "main",
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"output_tail": [
".........................[main e5428ad] rdm v2",
" 1 file changed, 1 insertion(+)",
" create mode 100644 marker",
"To /tmp/svc-db-regress-e8y1ohso/rdmremote.git",
" d78f4bf..e5428ad main -> main",
"..........................ssssssssssssssss",
"----------------------------------------------------------------------",
"Ran 67 tests in 41.981s",
"",
"OK (skipped=16)"
],
"output_sha256": "ea93eda4d2d37fcbe97d826cdda982a1a9274f27284dd7a930d09117de251e04",
"receipt_sha256": "0279d9b351beb2e54b662d4bc98b2b492c3898e931bd2a995ddb31c3e70f0fdb",
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/0279d9b351beb2e54b662d4bc98b2b492c3898e931bd2a995ddb31c3e70f0fdb.json"
}
```
| 同步(记录前对象) | 7e6c544b3c25c510e3d928bfb4515937a1cd6696 | 85624242789c823b96f2adb080e11833f73352e3 | 2026-10-20T11:41:24Z |
| 同步(记录前对象) | 33d6ef69212595c680a744549986c19bb6acd6c3 | 1b286ec3e68260d9d25cb3ab651ede31143d5b06 | 2026-10-22T06:35:19Z |
@@ -0,0 +1,72 @@
# Independent Code Review — feature/audit-followups-pin-gate-m5
- Reviewer: independent CLI review session (audit-followups-independent-review unit; fresh session, not the implementer)
- Date: 2026-10-22
- Review object: this checkout @ `a2b4cf73` (branch tip `feature/audit-followups-pin-gate-m5`, detached HEAD) versus `origin/main`. No `origin/*` refs exist in this sandbox, so the diff was taken against the merged authoritative main tip `d65195bd` (PR #691 merge, second parent of `a874ee60` "Merge authoritative main into the task branch"); `merge-base(d65195bd, HEAD) = d65195bd`, so `git diff d65195bd...HEAD` ≡ `git diff origin/main...HEAD`.
- Scope claims under review:
1. Dev: new `scripts/check_pin_regression.sh` wired into `scripts/check-pins.sh` and `scripts/task_pr_check.py`; tests `tests/test_check_pins.py` (+1 case) and `tests/test_check_pin_regression.py` (new, 13 cases). Motivating incident: PR #682 (stale branch silently resetting component pins over main).
2. `deps/agent-native` `cff69355` → `f2fb9ffa` (review-M5 fix: Writable preflight rejects subagent delegation).
3. Gitlink-only pin advances in `deps/agent-squad` `9edb6ba8` → `b6d87638` (nested native `f2fb9ffa`) and `modules/backend` `7bcbbdb3` → `a75783fd` (merged upstream `7bcbbdb` line + parallel work `443e7af`; nested native `f2fb9ffa`).
- Method: read-only review of `git diff d65195bd...HEAD` (9 files, +479/−4) plus per-submodule commit/tree diffs; focused checks run where this worktree permits (see Verification runs). No code was changed; the only write is this report.
## Findings
### Blockers
None.
### Major
- **MA1 — The CI leg of the pin-regression gate is a structural no-op; all enforcement rests on local full-clone runs.** All three CI checkout jobs fetch a raw object id shallowly and detach (`.gitea/workflows/ci.yaml:152,336,661` — `git fetch --depth=1 origin <sha>` then `checkout -q --detach FETCH_HEAD`), which writes only `FETCH_HEAD` and never creates `refs/remotes/origin/main` (empirically confirmed in a scratch repo: after a raw-SHA shallow fetch, `git rev-parse --verify origin/main^{commit}` fails). `check_pin_regression.sh:24-27` treats an unresolvable `origin/main` as "warn + exit 0" (documented shallow-checkout escape). Consequently `task_pr_check.py ci` (ci.yaml:162) and the CI `make test` → `test-pins` leg can never fail a PR on pin regression — the exact incident shape #682 (a stale branch merged through CI) would again pass CI today. Mitigation: the delivery policy mandates a local PR check before acceptance/merge, and in a full clone (`make task-pr-check local`) the gate does enforce (verified below). Not blocking — the change is a strict improvement and the local leg is the mandated enforcement point — but the gate's stated purpose ("gitlink 相对 origin/main 不得倒退" as a PR gate) is not achieved in CI. Recommendation: in the reviewed checkout step, additionally fetch the base ref (e.g. `git fetch --depth=1 origin "refs/heads/${GITHUB_BASE_REF}:refs/remotes/origin/main"`) so `pin_regression_problems()` verifies real ancestry at gate time; the submodule materialization already produces full child clones, so the per-component `merge-base --is-ancestor` checks would then work offline.
### Minor
- **MI1 — Gitlink removals relative to `origin/main` pass silently.** The gate iterates only HEAD's gitlinks (`check_pin_regression.sh:71-72,95`); a component dropped from the Dev tree (present on base, absent in HEAD) is never compared, and `check-pins.sh` likewise only checks the containers it iterates (a container dropping one of its `deps/*` links is equally invisible). Not the #682 shape (reset, not removal), and "new pin not on base" is an intentional pass (line 74-76), but silent composition shrinkage is adjacent to the incident class this gate exists for. Suggest a follow-up that diffs the base/HEAD gitlink path sets in both directions.
- **MI2 — `pin_regression_problems()` runs the gate with no subprocess timeout** (`scripts/task_pr_check.py:222-224`). `ensure_commit`'s `git fetch` (`check_pin_regression.sh:48-54`) can hang on a black-holed connection and would stall the PR check until the runner/job timeout. Low practical likelihood (unchanged pins short-circuit at line 73 before any fetch; CI submodules are full clones), but a `timeout=` on the `subprocess.run` would bound it.
- **MI3 — Unreachable-but-confusing fail mode if a CI submodule lacks the old pin.** If `ensure_commit` ever needed to fetch in CI, the component remotes persist only the public URL (credentials live solely inside the checkout step's per-command env), so the fetch fails and the gate fails closed ("missing locally and could not be fetched", lines 80-86) — correct direction, but the PR author sees an unfetchable-pin error whose real cause is credentials/absence. With full submodule clones this should never trigger; noting for diagnosis only.
### Notes
- **N1 — Preflight placement is consistent with the sibling F02 checks; "zero side effects" holds for the meaningful claims.** The new check (`deps/agent-native/adapters/native/backend.go:228-230`) sits inside the existing `len(writable) > 0 && profile == exec.ToolProfileWorkspace` block (line 216) before any registry/session/model work. Workdir preparation above it (`MkdirAll`, `INSTRUCTIONS.md`/`TASK.md` writes) predates rejection — identical to the pre-existing exec/MCP/host rejections, and `TestContractWritableRejectsSubagentInPreflight` asserts the substantive zero-side-effect properties: no stream call, no factory call, no deliverable file, no `.agentx` session state.
- **N2 — `pin regression ok` (line 100) prints even when `PIN_REGRESSION_SKIP=1` downgraded failures to warns.** Exit status is the contract (0 with warns), and the warns are loud on stderr; cosmetic only.
- **N3 — Pre-existing gofmt deviation in `adapters/native/backend.go`** (struct-literal alignment around `runtime.NewAgentSession`, ~line 447). The file at the base pin `cff69355` fails `gofmt -l` identically; the changed hunk (212-230) is clean. Out of scope for this fix.
- **N4 — Pre-existing stderr noise `fatal: not a tree object` when a container directory exists but is uninitialized.** Reproduced as `git -C deps/agent-intent ls-tree <sha>` from an empty dir discovering the parent Dev repo (process substitution in `check-pins.sh:68`); pre-dates this branch and does not change its exit status. In this sandbox only `modules/backend`, `deps/agent-native`, `deps/agent-squad` are materialized.
- **N5 — Go tests NOT_RUN in this workspace (environment, not code).** `deps/agent-native/go.mod:10,12` replace `error-code-system`/`logging-system` with nested checkouts that are not materialized here, and the pinned commit objects (`a3ee67bb`, `49b5d8be`) are absent from every local object store (Dev, native, squad, backend); network access is forbidden for this unit, so the modules cannot be completed. `GOWORK=off go test ./adapters/native/...` therefore fails at module setup, not in code. Fallbacks applied: both changed files parse (`gofmt -e`) and were line-reviewed; the author-recorded evidence (`verify-m5.json` / `verify-m5-host.json` exit=0, quoted in commit `f2fb9ffa`) is uncorroborated in this session and treated as author-claimed.
- **N6 — Composition coherence verified (lens 3).** Nested pins agree at every level: native `f2fb9ffa` (Dev `deps/agent-native` = squad `deps/agent-native` = backend `deps/agent-native`), squad `b6d87638` (Dev = backend), intent `765a7e71` (Dev `deps/agent-intent` = native = squad = backend), core `0cf03446` (Dev `modules/core` = backend `deps/agent-system-core`), command `fe7987dc`, document-renderer `3f259990`, error-code-system `a3ee67bb`, logging-system `49b5d8be`; guidelines/manager deltas (`2688fd2`/`67784f43`/`bd3a7de5` vs Dev `a15db508`/`2c2e3725`) are warn-only by design (`check-pins.sh:57-62`). `bash scripts/check-pins.sh` exits 0 ("pins ok") in this worktree.
## Spec lens (1) — gate catches the incident shape; M5 preflight is exact
- Gate trigger: HEAD pin not a descendant of the base pin fails — stale reset (`test_pr682_stale_branch_carrying_old_pin_fails`) and diverged/rebased pins (`test_diverged_pin_fails`) both exit 1 with `moves BACKWARD` + `PR #682` guidance; forward move, same pin, base-pin-absent (`new, not on origin/main`), unresolvable `origin/main`, and `PIN_REGRESSION_SKIP=1` downgrade all exit 0. `task_pr_check.py ci` wiring hard-fails (`PR_CHECK_FAILED`) on regression and passes with the skip env (`test_task_pr_check_ci_hard_fails_on_regression`, `test_task_pr_check_ci_skip_env_passes`). All 19 Python tests pass in this workspace (Verification runs).
- M5 preflight: the rejection condition is exactly non-empty Writable ∧ workspace profile ∧ delegation enabled (`backend.go:216,228-229`). Delegation exists iff `b.opts.Subagent != nil` (`runtime/subagent/subagent.go:62-71` no-ops on nil factory; `delegate_task` is registered only in the workspace branch, `backend.go:265`), and Writable reaches `Execute` only via `withWritable(ctx, req.Workspace.Writable)` (`adapters/native/contract.go:66`) — the reasoning profile has no registry at all. Empty-Writable delegation is covered by `TestContractSubagentWithoutWritableStillDelegates` (delegate_task declared, factory invoked with the request fields). Side-effect escape confirmed closed: `tools.ApplyWriteAllowlist` runs before `subagent.Register`, so `delegate_task` never inherited the write-set — rejecting the combination is the right call, and `run_tool_script` cannot reach it either (it only invokes registered tools and bans re-dispatch).
## Regressions lens (2)
- `check-pins.sh` behavior when `origin/main` is absent: unchanged — live run exits 0 with "pins ok", the gate only warns (`check_pin_regression.sh:24-27`). The appended call is `|| fail=1` (check-pins.sh:67), so any gate crash also fails closed.
- `task_pr_check` callers: no signature/behavior changes to existing functions (`PIN_REGRESSION` const and `pin_regression_problems()` are additive; `cmd_local`/`cmd_ci` append/inject problems only). `scripts/test_affected.py:61-62` and `scripts/task.sh:699` import unaffected names. Remote-failure `Stop` paths still precede the gate (fail-closed elsewhere). `make task-pr-check` runs from the repo root, matching the gate's cwd→toplevel resolution.
- Delegation flows without Writable: backend `a75783fd` and squad `b6d87638` contain zero `Writable`/`ContractRequest` usage in non-test Go code (grepped) — no live composition flow can trip the new rejection; the preflight only fires for contract requests that declare both.
## Scope lens (3)
Dev diff touches exactly the declared files plus the task card (`.task/**`, host domain): `scripts/check-pins.sh` (+2), `scripts/check_pin_regression.sh` (new, 100 lines), `scripts/task_pr_check.py` (+17/−1 docstring+gate), `tests/test_check_pins.py` (+17), `tests/test_check_pin_regression.py` (new, 187 lines). Submodule ranges are integration-only as declared: native = 1 commit (`f2fb9ff` "Reject subagent delegation under a Writable write-set", parent `cff6935`, no other content); squad = 1 gitlink-only commit (`b6d8763`, `diff-tree` = `deps/agent-native` only); backend = 3 commits (`3ab7cd7` and `92c2b4e` gitlink-only pins; `a75783f` = merge of `7bcbbdb` into the pin line; tree delta vs `7bcbbdb3` is exactly the two nested gitlinks; contains both `7bcbbdb` and `443e7af`). Nothing rides along outside the declared fixes.
## Tests lens (4)
New tests assert the trigger conditions, not just exit codes: exact rejection reason string, no stream/factory invocations, no deliverable/session files (native); `moves BACKWARD` / `PR #682` / `PIN_REGRESSION_SKIP=1` message substrings, `ok <path>` / `pin regression ok` / warn lines, and `[verdict] PR_CHECK_FAILED` / `PR_CHECK_OK` plumbing (Dev). `test_check_pins.py` additionally proves the gate is actually wired inside `check-pins.sh` (copies both scripts into the fixture). Offline discipline is good: temp-dir repos, hermetic env, `GIT_CONFIG_NOSYSTEM=1`, env vars scrubbed in setUp, local bare remotes for the fetch paths.
## Verification runs (this session, foreground)
| Check | Result |
|---|---|
| `git diff --stat d65195bd...HEAD` | 9 files, +479/−4 — matches declared scope |
| `bash scripts/check-pins.sh` | exit 0, "pins ok"; gate warn-only (no origin/main); guidelines warns only |
| `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_check_pin_regression tests.test_check_pins -v` | 19/19 OK (0.796s) |
| `cd deps/agent-native && GOWORK=off go test -count=1 ./adapters/native/...` | NOT_RUN — module setup fails on unmaterialized replace targets (`go.mod:10,12`); objects absent everywhere; network forbidden (see N5) |
| `gofmt -e` on both changed native files | parse OK; `gofmt -l` deviation pre-existing (N3) |
| Raw-SHA shallow fetch → `origin/main` resolvability experiment | does NOT resolve (basis of MA1) |
| `make test` | intentionally NOT run per unit packet (known pre-existing `test_svc.SvcDbRecoveryTests.test_addr_change_keeps_database_and_auth` governance failure on clean main `b7636742`, recorded on the task card; CI is the final arbiter) |
## Verdict
The pin-regression gate correctly detects the #682 backward-pin shape (and diverged pins) while passing forward/same/new-pin moves and degrading safely without `origin/main` or via the owner-reviewed skip; the M5 preflight rejects exactly the unenforceable Writable+delegation combination with zero behavioral fallout for empty-Writable or non-contract flows; composition pins advance cleanly with no source riding along; the new tests assert real trigger conditions. The one substantive weakness (MA1 — CI leg cannot enforce due to the shallow raw-SHA checkout) is an enforcement-depth gap on a strictly-improving change whose mandated local enforcement point works; MI1–MI3 are bounded follow-ups.
REVIEW VERDICT: APPROVE
+3 -1
View File
@@ -41,7 +41,9 @@ execution:
max_attempts_per_profile: 3
roles:
implementation: [cursor, antigravity, pi-glm, pi, codex]
# cursor 在 U1 单元已耗尽 4 次尝试:1 次传输中断、2 次终态 JSON 后附带尾文、1 次模型列表瞬时为空。
# 临时调整,交付前回退。
implementation: [antigravity, pi-glm, pi, cursor, codex]
repair: [cursor, antigravity, pi-glm, pi, codex]
test: [pi-glm, pi, antigravity, cursor, codex]
review: [pi-glm, pi, antigravity, cursor, codex]
+6 -1
View File
@@ -2,7 +2,10 @@
- 用途:开发集成父仓(gitlink 权威);非分发单元。
- 组合:`modules/`(产品组件)+ `deps/`(共享依赖);完整组件清单以 [`.gitmodules`](.gitmodules) 为准。
- 部署:非容器;Releases 仓从本仓固定提交导出版本。
- 部署:非容器为现行默认(`storage_mode=legacy`);容器化存储改造见
[容器化存储方案](docs/superpowers/plans/2026-10-22-container-storage-plan.md),
环境与数据归属合同(U1)见 [deploy/data-ownership.md](deploy/data-ownership.md)。
Releases 仓从本仓固定提交导出版本。
## 验证
@@ -56,6 +59,8 @@
[计划](modules/documents/docs/proposals/2026-10-12-work-companion-ui-plan.md) 覆盖任务摘要、对话/输入、窄屏和真实状态投影。
- [服务重启与全量数据恢复](modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md):svc 启停、异常退出后的依赖、配置/数据身份、任务/HITL/副作用、文件与组合备份恢复;
U8 规格见 [SQLite 实例守护](docs/recovery/u8-sqlite-instance-guard.md)。
- [单一应用二进制与容器化数据库、知识库存储](docs/superpowers/plans/2026-10-22-container-storage-plan.md):两容器三库、外置文件根、环境独立绑定;
U1 归属清单见 [deploy/data-ownership.md](deploy/data-ownership.md)。
- [svc 统一部署与一次性登录 Key](modules/documents/docs/proposals/2026-10-12-svc-unified-deploy-and-login-key.md):
二进制/源码统一部署、环境准备、无初始密码开户与账户恢复;规格见 `docs/svc-deploy/`,
账户与恢复续审见 [续审记录](docs/svc-deploy/2026-10-12-account-durability-plan-review.md)。
+40
View File
@@ -0,0 +1,40 @@
# 容器化存储:数据归属清单(U1)
权威设计:`docs/superpowers/plans/2026-10-22-container-storage-plan.md`(U1、KTD1—KTD4)。
实例身份与期望态唯一权威:`runtime/<name>/manifest.json`(`scripts/svc_manifest.py`);
本清单只登记**数据所有者、类型、引用形式与唯一写者**,不含秘密值与业务内容。
生产体量/源版本盘点需另行授权访问后补全;未核实项不得据此声明迁移完成。
## 归属总表
| 所有者 | 类型 | 引用形式(登记键) | 唯一写者 | 迁移范围 |
|---|---|---|---|---|
| 基座逻辑库 | PostgreSQL 逻辑库(账号/项目/消息/文档/设置等基座表族) | `base_dbref` 或 `base_dsn_ref`(绝对路径秘密文件)+ `base_role_ref` + `base_port`;身份锚 `identity.base_db_ref` | 本实例应用进程(经基座 DSN);引导/迁移角色与应用角色分离(U2) | 必需 |
| RDM 领域库 | PostgreSQL 逻辑库(RDM 域表/项目文档树等) | `rdm_dbref` 或 `rdm_dsn_ref` + `rdm_role_ref` + `rdm_port`;锚 `identity.rdm_db_ref` | 本实例应用内 RDM 域(独立 DSN);非基座连接串 | 必需 |
| 知识库 | PostgreSQL/pgvector 逻辑库(语料、索引投影) | `kb_dbref` 或 `kb_dsn_ref` + `kb_role_ref` + `kb_port`;锚 `identity.kb_db_ref` | 本实例知识库接入(应用内);索引可重建但语料库为权威 | 必需 |
| 持久文件根 | 目录树(工件、RDM uploads、KB 原件及必要解析产物) | `files_root`(**绝对路径**,不相对 checkout/worktree 解析);锚 `identity.files_root` | 本实例应用进程;禁止他实例共用同一根 | 必需 |
| 环境身份 | 枚举 | `env_class` ∈ `prod`\|`test`\|`verify`;锚 `identity.env_class` | 运维显式登记;克隆时换新(U5) | 必需(container) |
| 实例身份 | 稳定 ID + 数据 marker | `instance_id`;`<data_root>/.agentx/data-marker.json` | `svc` manifest 治理;启动只校验不重建 | 必需 |
| Compose 项目 | 容器项目名 | `compose_project`;同 runtime 内唯一 | 本环境容器驱动(U2);冲突在修改前拒绝 | container 必需 |
`storage_mode=legacy`(缺省):沿用既有 SQLite / 宿主 PG(`db`/`dbref`/`dsn`)路径,不要求三库与 `files_root`。
`storage_mode=container`:上表 container 行全部必需;缺任一绑定、错库、错文件根、foreign marker、同名 Compose 或端口冲突 → **拒绝**,不静默建空库、不回退默认。
凭据只允许以受保护文件路径引用(`*_dsn_ref` / `*_role_ref`),明文不得入 manifest、日志或本清单。
## 配置形态
见同目录 [`env-bindings.example.env`](env-bindings.example.env)。写入实例:
```bash
# 先写齐绑定键,最后打开 container 合同(缺一即拒)
scripts/svc.sh <name> … # 经 svc_set_key / manifest set;Compose·端口冲突在修改前失败
```
另一 checkout/worktree 只要指向同一绝对 `files_root` 与库引用,仍定位同一资源(路径不相对仓库根解析)。
## 与旧恢复清单关系
细粒度基座表/插件枚举仍见 [`docs/recovery/u1-data-ownership.md`](../docs/recovery/u1-data-ownership.md)。
本文件是容器化三库+文件根的**合同级**归属;实施迁移(U3)时两者对照,必需所有者未覆盖则阻塞切换。
+31
View File
@@ -0,0 +1,31 @@
# 容器化存储实例绑定示例(U1)——写入 runtime/<name> 经 manifest 权威登记。
# 秘密只引用受保护文件路径,不在此填写明文密码/DSN 值。
# 完整合同见 deploy/data-ownership.md;schema 键由 scripts/svc_manifest.py 校验。
# legacy(缺省)| container(三库+文件根+环境身份全部必需)
storage_mode=container
# 环境身份:prod | test | verify(三选一)
env_class=prod
# 持久文件根:必须是绝对路径(不相对代码 checkout/worktree 解析)
files_root=/var/lib/agent-system/state/prod/files
# Compose 项目名:同机各环境唯一;冲突在修改前拒绝
compose_project=agentx-prod
# 基座逻辑库:dbref=<pg实例>/<库名> 或 dsn_ref=<绝对路径秘密文件>(二选一)
# base_dbref=pg-business/agent_base
base_dsn_ref=/etc/agent-system/prod/base.dsn
base_role_ref=/etc/agent-system/prod/base.role
base_port=15432
# RDM 领域库
rdm_dsn_ref=/etc/agent-system/prod/rdm.dsn
rdm_role_ref=/etc/agent-system/prod/rdm.role
rdm_port=15432
# 知识库(pgvector 容器独立端口)
kb_dsn_ref=/etc/agent-system/prod/kb.dsn
kb_role_ref=/etc/agent-system/prod/kb.role
kb_port=15433
@@ -0,0 +1,266 @@
---
artifact_contract: ce-unified-plan/v1
product_contract_source: ce-plan-bootstrap
execution: code
date: 2026-10-22
title: 单一应用二进制与容器化数据库、知识库存储改造方案
---
> **落库状态(2026-10-22)**:方案已获审阅并授权实施。任务以 L1 initiative
> `feature/container-storage-plan` 重新登记(R0=955a32de2bb49171348bad5f566cce935df19212)。
> 原始规划工作区(基线 9a80897)因子模块物化失败已保留现场并对账后回收:
> 全部子模块 HEAD 均在远端分支上、无独有提交、无未跟踪文件,任务卡副本存于
> `/tmp/container-storage-plan-20261022/card-auto-generated.md`。本文正文为审阅时快照,
> 其中基线 9a80897 与工作区失败描述属历史记录;实施以本卡与各子任务卡为准。
# 单一应用二进制与容器化数据库、知识库存储改造方案
本文是待用户审阅的方案与实施计划,不代表业务实施、部署或迁移授权。应用二进制及数据库容器的总体方向来自本次会话;具体分工、步骤与验收为本方案建议。
计划归属 Agent-System-Dev;本文因新工作区子模块物化失败暂存仓外,尚未提交或合入。路径以各标明仓库的根目录为基准。后续正规落点建议为 Dev `docs/superpowers/plans/2026-10-22-container-storage-plan.md`。
## Goal Capsule
- **目标**:运维人员能重建生产、测试、验证环境,把一个完整环境迁到新机器或新目录,并通过恢复验证确认账号、项目、文档、文件和知识库引用仍可用。
- **手段**:一个应用服务二进制,两个 PostgreSQL 容器,三套逻辑数据库,外置持久文件;同一制品使用独立环境配置(KTD1—KTD4)。
- **当前授权**:制定方案和计划。产品代码、容器部署、生产数据读取/复制、迁移切换均未在本轮执行;后续按用户明确授权范围实施。
- **执行责任**:各仓遵循自身 AGENTS;Dev 范围的可独立实施单元按 AGENT-EXECUTION.md 分配全新外部 Agent 会话,host 管理范围、验证、独立 Review 和交付。任务进度放任务卡,不在本文伪装已完成。
- **停止条件**:需要新增数据库类型、改变业务权限、无法完整迁移现有数据、需要丢弃新写入或扩大业务范围时停止相关动作,提交最小决策;不为容器化引入高可用平台、通用运维平台或应用微服务化。
## Product Contract
### Summary
保留前后端、Agent、RDM、知识库接入在一个应用进程内;数据库依赖使用 Compose。业务 PostgreSQL 容器分别承载基座库和 RDM 领域库,知识库 PostgreSQL/pgvector 容器承载语料与检索索引。生产、测试、验证环境分别建立,不通过临时修改生产连接串来“切环境”。
### Problem Frame
现有宿主数据库运维、应用快照、RDM 领域备份和知识库恢复各有入口,但尚未形成可核验的完整环境恢复闭环。容器化只改变数据库运行方式,必须同时处理身份绑定、数据库与文件的一致恢复点,以及复制环境后的副作用隔离。
### Requirements
| ID | 要求与可核验结果 |
|---|---|
| R1 | 完整应用服务只需一个可执行文件,包含前端资源、后端、Agent、RDM、知识库接入;运行机无需源码、Go 或 Node 构建环境。 |
| R2 | 业务库和知识库存储容器化,明确持久卷、镜像版本、扩展版本及生命周期;容器重建后业务数据仍在。 |
| R3 | 每环境独立绑定基座库、RDM 库、知识库、文件根、实例身份、端口和凭据;错误绑定拒绝启动。 |
| R4 | 从备份恢复到新目录/主机后,账号、权限、业务记录、原文和历史引用可核对;成功备份必须是一个可解释的一致恢复点。 |
| R5 | 从当前实际 SQLite/宿主 PG 迁移时覆盖所有必需持久数据所有者,保留业务标识、引用与原始内容,缺失或未知数据阻止切换。 |
| R6 | 测试/验证克隆获得新实例身份及独立凭据,启动前隔离生产密钥、会话、自动任务和外部副作用;生产实例保持固定绑定。 |
| R7 | 普通启停、重建、升级不隐式删卷、不执行数据库大版本升级、不恢复旧备份、不在数据库失败时回退到空 SQLite。 |
| R8 | 形成可重复的组合验证和生产切换/回滚操作记录;实现完成、测试通过、生产迁移完成、业务验收通过分别记录。 |
### 边界与假设
- “单一二进制”按用户原话“最终的服务”解释为一个常驻应用服务。允许部署配置、脚本和离线迁移工具存在;不新增常驻 admin、KB HTTP 或前端服务。本期不为统一离线工具而重构全部 CLI。
- 一期面向单机部署,每套环境独立;可在同机不同端口运行,也可迁到不同主机。宿主应用通过回环地址连接本机容器。
- 知识库容器化指语料数据库及向量索引容器化;检索编排、权限与引用业务仍在应用二进制内。原始文档使用外置持久存储。
- 允许维护窗口,先实现可验证的停写备份与迁移,不承诺零停机、实时复制、PITR 或自动故障切换。
- 保留既有 SQLite/原生 PG 运行路径用于兼容与受控回退;本方案目标环境使用容器 PG,不顺带删除旧实现。
- 公有知识库共享、跨环境共用只读卷、Kubernetes、托管云数据库、全量对象存储替换均不进入首期。
- 生产停写窗口、源实例、机器容量、实际源版本与数据规模在 U1 核实;它们不妨碍方案审阅,但未核实前不能安排生产切换。规划不承诺未经测量的 RTO/RPO。
## Planning Contract
### 当前依据与可复用基础
本轮主仓基线 Dev `9a80897cb7e9108091d302e849f59662f21ce260`。以下是源码/文档核查,未探测实际生产实例,也未运行产品测试。
| 范围 | 已核对的依据 | 本次改造含义 |
|---|---|---|
| 单二进制 | Dev `Makefile`;Backend `web/embed.go`;RDM `main.go`、`scripts/build-product.sh` | 已有静态组合和前端嵌入,复用完整产品构建;不能用只有基座的二进制代替 RDM 产品。 |
| 宿主 PG | Dev `scripts/svc.sh` 的 pg_bindir/db_create/pg_do_start/pg_do_stop/pg_run/pg_dump_run/db_backup/db_restore/db_clone | 容器支持需要完整后端分派,不能只替换启动命令;备份恢复继续使用已有安全边界。 |
| 实例身份 | Dev `scripts/svc_manifest.py` 的 PROTECTED/db_anchor_of/reanchor;`tests/test_svc_identity.py` | manifest 已有权威身份;扩展三库和环境绑定,不另造第二份登记权威。 |
| 基座迁移 | Backend `cmd/storage-migrate/main.go` | 已有 SQLite→PG 工具,但只迁 Backend;artifacts 参数只盘点不复制,不能据此声称迁了整个产品。 |
| RDM 迁移 | RDM `main.go` 的 migrate-to-pg/migrate-check/backup-domain/restore-domain | 独立 RDM_DSN;RDM 领域库和 uploads 必须明确纳入。 |
| KB Compose | KB-Regulations `deploy/compose.yaml`、`deploy/README.md` | 已有本地 pgvector/pgvector:pg16、回环端口、卷与扩展初始化模板;固定项目名 kbreg 不适合作为所有环境的全局名称,已有标签不等于生产版本批准。 |
| 文件迁移 | Backend `internal/blobstore/blobstore.go`;KB-Regulations `migrations/0001_schema.sql`、`docs/operations/corpus-import.md` | 工件目录相关引用、source_assets.storage_uri 中 file 路径会影响异地恢复;不能只做 dump/restore。 |
| 组合恢复 | Dev `scripts/svc.sh` 的 agent_backup_snapshot;`tests/integration/regulations-kb/README.md` | 应用快照不含全套数据库和原始 manifest;KB 测试可复用,但不证明全套外部文件已恢复。 |
RDM 研究对象为兄弟检出 `b14c84c6dcc945bd4d0a9ad446cf1ec980396491`,KB 为 `393555bec8d964f294f932616aa69ab833a06de1`;它们与 Dev `tests/integration/regulations-kb/components.pin` 的固定组合不同。本方案引用它们用于定位能力,正式实施前必须选定并验证精确组件组合,不能把研究检出当作已采纳版本。
已有恢复设计 `modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md` 是设计约束来源,本文沿用其停写一致恢复点、恢复新目标、旧写者隔离思路,不把历史执行状态当现状。
### Key Technical Decisions
**KTD1.应用保持单一服务二进制。** 复用现有产品组合构建,配置和数据外置;容器承载持久数据库依赖,知识库插件不拆进程。落实 R1/R2。(session-settled: user-directed — chosen over containerizing the whole application: user requires the final service to remain a single binary)
**KTD2.两个 PG 容器、三个逻辑库。** 业务容器中分别设基座库与 RDM 库,知识库另用带 vector/pg_trgm 的 PG 容器。数据库使用独立应用角色;引导/迁移角色与应用权限分开,先核对现有启动迁移要求,再安排显式迁移阶段,不能直接收紧权限导致启动失败。落实 R2/R3/R5。
**KTD3.复用 svc 管理入口。** 新增窄的容器驱动和版本化环境配置,保留现有命令语义。manifest 是身份与期望态权威;Compose 文件是版本化模板,环境参数由同一配置解析生成。不得并存两份可独立修改的 DSN/卷权威。只管理明确归属本环境的容器,不启停外部数据库。落实 R3/R7。
**KTD4.稳定绝对数据根与环境独立资源。** 建议形态为 `state/<env>/` 与其外部 `backups/<env>/`,实际根由配置给出绝对路径;不相对代码 checkout/worktree 解析。Compose project、两套卷、各库、角色与端口都显式区分环境。凭据放受保护文件/secret 引用,日志和清单脱敏。文件树示意仅表达归属,不规定最终名称。
**KTD5.先备份完整恢复点,再追求不停机。** 关闭新写入口,排空或隔离在途任务,停止应用、定时任务、KB 导入/索引写者及迁移器;确认其他客户端不再写入后,对三库分别逻辑导出并复制受控文件。三次 dump 各自的快照不自动构成跨库一致性,靠整个窗口无写者保持一致。完成校验后才发布恢复包完成标记。落实 R4/R8。
**KTD6.恢复和克隆是不同操作。** 原实例恢复保留业务与实例身份,但须先停旧写者,绝不双开同一生产身份。测试克隆保留业务 ID 及引用关系,创建新的部署身份和守护绑定、数据库凭据;使生产登录会话/长期 API key 在副本失效,外部 provider 密钥不带入。后台任务与外发能力默认关闭,授权后逐项启用;不通过批量改业务 ID 实现隔离。落实 R3/R6。
**KTD7.迁移与升级分离。** 源 PG 采用同大版本兼容目标与匹配工具链,SQLite 迁移用已有逻辑迁移器;本次不顺带 PG 大版本升级或强制统一业务/KB PG 大版本。实施时固定经过验证的镜像 digest、平台与扩展版本。镜像官方文档说明不同主版本的 PGDATA/卷位置存在差异,配置按选定镜像逐项核对,不能把 KB pg16 的挂载路径盲用到另一主版本。初始化脚本仅负责新卷引导,既有库变更走显式迁移。落实 R2/R5/R7。
### High-Level Technical Design
以下是关系和流程草图,新增配置/命令的最终命名由实现单元在既有接口中确定。
```mermaid
flowchart LR
Client[浏览器] --> App[一个完整应用二进制]
Profile[环境配置与实例 manifest] --> App
Profile --> Compose[Compose 数据依赖]
App --> Base[业务容器 / 基座库]
App --> RDM[业务容器 / RDM 领域库]
App --> KB[知识库容器 / 语料库与索引]
App --> Files[持久文件根 / 工件 uploads 原件]
Compose --> Base
Compose --> RDM
Compose --> KB
```
```text
state/<env>/
instance/ manifest 与受保护配置、应用版本引用
files/ 工件、RDM uploads、KB 原件及必要解析产物
Docker 持久卷:<project>-business、<project>-knowledge(各环境独立)
backups/<env>/<backup-id>/ 三库导出、文件、配置恢复材料、版本与校验清单
```
```mermaid
flowchart LR
A[识别目标与锁定操作] --> B[预检配置/身份/镜像/资源]
B --> C[启动两库容器]
C --> D[认证/扩展/schema/三库身份检查]
D --> E[按需执行显式迁移并核验]
E --> F[启动应用]
F --> G[业务就绪验证]
D -->|失败| H[拒绝启动并保留现场]
```
```mermaid
stateDiagram-v2
[*] --> Running
Running --> Quiescing: 请求一致备份
Quiescing --> Frozen: 写者已停且在途状态已记录
Frozen --> Capturing: 三库与文件导出
Capturing --> Verified: 完整性和清单校验
Capturing --> Incomplete: 失败或中断
Verified --> Running: 发布完成标记后恢复原实例
Incomplete --> Running: 确认现场可安全恢复后解除停写
```
恢复流程:读取完成清单 → 校验兼容版本与文件 hash → 选择空的新目标 → 建库/恢复角色权限及三库 → 还原文件与受控路径映射 → 核对实例模式(原实例/克隆)→ 禁止外部副作用下启动 → 登录、权限、文档、KB 引用验证 → 单独决定是否开放写入。任一步失败保持目标隔离,不覆盖旧系统。
### 完整恢复包合同
- 基座、RDM、KB 三库的逻辑导出及 schema/扩展版本;必需角色/权限用受控恢复定义记录,不能盲目导入生产全局超级用户。
- 工件、uploads、KB 原件及必要解析产物;包含全部必需插件声明的数据。对对象存储记录不可变版本/校验值及可恢复获取方式;无法获取的必需对象阻止完整恢复声明。
- 原始实例 manifest、部署配置、应用制品及插件/前端组合的 SHA、容器镜像 digest、平台信息;配置清单不含明文秘密,恢复秘密通过独立受保护介质或重新注入。仅含引用时必须验证引用在目标可解析,不能称恢复包独立可恢复。
- 文件清单与 hash、数据所有者清单、停写时间和操作状态、恢复工具版本、校验结果及完成标记。备份与 runtime 卷分离,至少验证一份异机/独立存储副本可读取。
- 原件路径变更仅调整定位信息,不改变内容 hash、语料修订、节点 ID、历史 excerpt 和审计封印。受保护审计内容若包含原路径,必须通过解析映射兼容,禁止重写封印记录。
- 向量索引可重建,但 embedding 模型/维度和语料版本要记录;恢复测试不依赖付费重新嵌入,优先复用保存的向量重建索引。
## Implementation Units
### U1.环境与数据归属合同
- **需求**:R1/R3/R5/R6,KTD1—KTD4。
- **目标**:一份可解析配置确定完整实例资源,明确现有数据来源和迁移边界。
- **主要文件**:Dev `scripts/svc_manifest.py`、`scripts/svc.sh`、`docs/svc.md`、`PROJECT.md`;新增环境 schema/示例置于 Dev `deploy/`(建议路径)。
- **做法**:扩展现有 manifest 绑定基座/RDM/KB,版本化旧配置升级;保护环境命令不受调用者环境变量污染。列出各数据所有者、类型、源版本、目录、体量、表/对象清单、引用形式和唯一写者。不输出秘密或业务内容。生产盘点需先获得对应访问授权。
- **测试文件**:扩展 `tests/test_svc_identity.py`、`tests/test_svc_lifecycle_isolation.py`、`tests/test_svc_precheck.py`。
- **场景**:旧配置升级保留身份;错库/错根/foreign marker 拒绝;同名项目或冲突端口在修改前失败;在另一 checkout 调用仍定位同一资源;三库中任一必需绑定缺失不能静默新建空库。
- **完成条件**:配置、归属清单和迁移范围有唯一解释;明确需迁移的每个数据所有者。依赖:无。
### U2.容器数据库与 svc 接线
- **需求**:R2/R3/R7,KTD2—KTD4/KTD7。依赖 U1。
- **主要文件**:Dev `scripts/svc.sh`(后端分派)、新增 `scripts/svc_containers.sh` 和 `deploy/compose.storage.yaml`(建议路径);KB-Regulations `deploy/compose.yaml`、`deploy/initdb/01-extensions.sql`、`deploy/README.md` 作为复用来源,避免复制成两套互相漂移的模板。
- **做法**:start/stop/status/SQL/dump/restore 覆盖同一容器驱动;记录明确所有权;卷重建、普通停止和数据销毁分别处理。应用仅连 loopback 发布端口。引导创建基座/RDM 独立库与角色,KB 创建扩展;后续变更使用显式迁移。启动顺序验证真实认证、扩展和身份,不能只依赖 pg_isready。
- **测试文件**:新增 Dev `tests/test_svc_containers.py`;扩展 `tests/test_svc_precheck.py`、`tests/test_svc_realcombo.py`。
- **场景**:prod/test/verify 同时运行互不冲突;停止/重建一套不影响其他套且不丢数据;缺 Docker/镜像/磁盘/凭据时在破坏性动作前失败;密码变更不误以为改 env 就改变已初始化库;容器意外停止时应用不得写入 SQLite。
- **完成条件**:用固定制品及临时环境完成两容器三库启动、持久化、重建与失败路径验证。运行冒烟优先,不用大量模拟测试替代真实容器验证。
### U3.迁移与文件引用兼容
- **需求**:R4/R5/R7,KTD5—KTD7。依赖 U1/U2。
- **主要文件**:Backend `cmd/storage-migrate/`、`internal/blobstore/blobstore.go`;RDM `main.go` 与其现有领域迁移实现;KB-Regulations 的 source_assets 读取/原件定位实现及 `docs/operations/corpus-import.md`。函数名在实施启动时定点核对,不另建并行迁移框架。
- **做法**:基座和 RDM 分别复用现有迁移器;覆盖实际 schema 的新增表、外键、序列/标识和身份记录;未知表不能悄悄遗漏。已有 PG 走兼容逻辑恢复。文件迁移校验内容和所有权,新增受控资产根映射或兼容相对引用,维持旧引用可读取。禁止迁移时跨出允许根或追随外部软链复制未知文件。
- **测试文件**:Backend `cmd/storage-migrate/main_test.go`、`integration_test.go`、`seeded_singletons_test.go` 与 `internal/blobstore/` 对应测试;RDM 现有领域迁移测试(实施前定位);KB-Regulations `tests/integration/recovery_pg_test.go`。
- **场景**:SQLite 基座+RDM 迁入不同逻辑库;原 PG 同版本恢复;空库与真实结构 fixture;失败/重跑/非空目标拒绝;未知表拒绝;不同根目录恢复后读取旧工件与 file URI 原件;越界路径拒绝;历史 citation/审计 hash 不变。
- **完成条件**:逐所有者对账,不仅比较行数,还核对 ID、关键引用、内容 hash、权限和新写入;不能完成的必需所有者阻止继续生产准备。
### U4.组合备份与恢复新目标
- **需求**:R4/R5/R8,KTD5/KTD7。依赖 U2/U3。
- **主要文件**:Dev `scripts/svc.sh` 的 backup/restore 入口;新增组合恢复辅助实现(建议 `scripts/svc_bundle.py`);`docs/svc.md`、恢复操作说明;各组件只提供必要导出/校验接口。
- **做法**:按恢复包合同采集全部数据,锁定同环境操作,停写并确认所有写者;失败包无完成标记。完整备份默认只恢复到空的新目标;应用制品和 schema 兼容预检先于开放写入。秘密处理有明确备份/重新注入路径及权限校验。
- **测试文件**:扩展 `tests/test_svc_backup.py`;新增 `tests/test_svc_bundle.py`;扩展 `tests/ops/run_backup_drill.sh` 的不同目标根场景。
- **场景**:三库与文件可恢复;备份中断、空间不足、错包、hash 损坏、缺原件、缺必要 secret、镜像不兼容拒绝;目标非空拒绝;不得覆盖生产;重新启动源实例不把未完成包发布为成功;保留原生产身份恢复时,目标接管前必须隔离旧写者;独立克隆从已完成的一致备份恢复,源实例可在备份完成后继续运行,但目标首次启动前必须完成新身份、独立凭据及副作用隔离。
- **完成条件**:干净环境恢复完整产品并生成可复核清单;实测备份大小、停写时长和恢复用时。
### U5.测试/验证克隆与环境选择
- **需求**:R3/R6/R8,KTD4/KTD6。依赖 U4。
- **主要文件**:Dev `scripts/svc.sh` 的 clone/启动接线、`scripts/svc_manifest.py`;Backend/RDM 现有身份、会话和任务控制入口仅作必要接线;测试数据 fixture 与说明。
- **做法**:以明确目标环境创建新实例。业务 ID 不改,部署身份、守护与凭据换新;登录会话/API key 不在副本继续有效,账号密码处理按克隆用途明确。需要复制生产敏感数据时先落实访问范围和脱敏策略;常规自动验收优先合成数据。外部密钥、自动调度与恢复任务副作用默认禁用,并在首次启动前生效。
- **测试文件**:扩展 `tests/test_svc_identity.py`、`tests/test_svc_lifecycle_isolation.py`;新增 `tests/test_svc_environment_clone.py`。
- **场景**:克隆后生产仍正常;新身份不触发原守护冲突;旧 token 无法访问副本;待执行任务不自动发信/调用真实 provider/写生产目录;选择 test 不修改 prod 配置;操作摘要明确目标环境。
- **完成条件**:三套环境同机并存,隔离由实际读写与副作用拒绝验证,不靠名称或配置声明。
### U6.单二进制与组合验收、运维交付
- **需求**:R1—R8,KTD1—KTD7。依赖 U1—U5。
- **主要文件**:Dev `Makefile`、`tests/test_svc_realcombo.py`、`tests/integration/regulations-kb/run.sh`、`components.pin`、`PROJECT.md`、`docs/svc.md`;RDM `scripts/build-product.sh`;各仓必要 runbook/组合指针。
- **做法**:固定完整产品与镜像组合,在没有源码及 Go/Node 的机器/隔离环境用同一应用制品启动。把恢复、克隆、数据库容器重启加入组合验证。必要管理操作优先复用同一产品已有入口,离线工具不成为常驻依赖。受影响仓独立验证后再推进 Dev 组合,不重做发布体系。
- **场景**:UI/API、正常账号登录、角色边界、项目/会话、RDM 上传/文档原件读取、知识库检索、历史引用解析;两库容器依次重启;新目录恢复;test/verify 重建不影响 prod;客户端制品无 secret。
- **完成条件**:满足 Definition of Done;输出生产切换操作单与回滚分界,实测停写时间供用户安排窗口。生产执行属于下面 O1,不由代码测试替代。
## Verification Contract
- 本轮是规划:产品测试、真实容器试验、迁移演练、生产盘点/部署均为 **NOT_RUN**;源码核查属于 SELF,不能提升为 GATE 或 REPLAY。
- 实施每单元先运行其最小直接检查,容器与文件恢复必须有真实临时环境验证。禁止把缺 DSN 导致的跳过计作通过;真实测试仅指向显式临时环境。
- Backend 真 PG 迁移测试使用 `AGENT_PG_TEST_DSN`;法规组合门禁使用 `KBREG_TEST_DATABASE_URL`、精确 RDM/KB pin 及既有 `make test-regulations-kb`。
- Dev 治理脚本确有修改时运行 `make test-governance`;最终组合按项目要求执行 `make test`,并以当前 CI 要求核对 `test-pg`、`svc-realcombo` 等检查,不能凭旧清单声称全部通过。
- 推进 Dev gitlink 前完成所需组合验证;命令原始输出经 `make evidence` 写任务卡。正式 Review 前重放最小集合、同步权威目标;合入信任来自精确 HEAD 的必需 CI 或独立 REPLAY。
- 全量检查只在最终候选或有必要时运行;发现无关非阻塞问题不扩大本次任务。
## O1.生产迁移与回滚计划(另行授权执行)
1. **确认源与窗口**:记录实际源实例、三库/文件清单、schema、版本、容量、现有身份、备份位置及授权范围;确认恢复演练达标和可接受停写时间。
2. **提前准备目标**:拉取固定镜像、准备空库/角色/持久目录、验证磁盘与网络;用演练副本验证完整应用制品。此阶段不改生产连接。
3. **冻结生产写入**:暂停入口及所有后台/外部写者,排空或隔离在途工作,确认旧实例不会被 supervisor 自动拉起;记录已确认写入的对账点。
4. **最终备份与迁移**:按 U3/U4 生成最终完整包并恢复目标;核对所有必需数据与文件,运行只读业务验证;保持外部副作用关闭。
5. **受控切换**:以迁移操作更新目标绑定、身份和文件路径;先验证登录/权限/文档/知识库,再开放写入及经确认的后台任务。避免两个生产写者同时存在。
6. **观察与保留**:保留旧数据及最终备份,只读保护并记录新系统写入开始时间;到达业务验收及约定保留期后才单独安排旧资源回收。
回滚分界必须写入操作单:
- **新目标尚无业务写入**:停止新实例,确认无新数据后恢复旧配置/旧写者;不可让双方同时写。
- **新目标已有业务写入**:停止写入并保存新数据,禁止简单切回旧库;评估向前修复或经过验证的数据对账/回迁。未经确认不丢弃新写入。
- **schema 已改变**:旧应用制品不一定兼容。代码回滚、数据库恢复和业务数据回迁分别验证,不能用“换回旧二进制”代替恢复方案。
## Definition of Done
代码交付完成须同时满足:
- 一个完整应用服务二进制在目标运行环境启动,业务/RDM/KB 数据存储符合两容器三逻辑库边界。
- prod/test/verify 可独立启动、停止、重建与选择,配置/数据/身份/权限隔离已验证。
- 完整备份可以恢复到不同根目录;原文可读、业务引用和历史法规引用不变;缺失数据时明确失败。
- 真实迁移 fixture 对账通过,重启与容器重建不丢数据,失败不会回退空系统或启动外部副作用。
- 精确组件组合、测试证据、独立 Review、所需 CI 及规范文档齐全;没有遗留试验实现进入最终 diff。
- O1 的执行另记:代码完成不等于生产迁移完成,恢复演练也不等于业务验收。
## 顺序与建议里程碑
| 里程碑 | 工作 | 出口 |
|---|---|---|
| M1 最小可运行 | U1 → U2 | 同一应用连接两容器三库,test/verify 并存,持久化与错误绑定验证通过 |
| M2 可迁移可恢复 | U3 → U4 | 完整数据恢复到新目录,原文和历史引用可核对 |
| M3 可安全切环境 | U5 → U6 | 克隆隔离、完整产品组合验证、操作说明齐备 |
| M4 生产落地 | O1 | 获得窗口授权后迁移、业务验收,旧数据按约定保留 |
U1 完成后再按实际 schema 覆盖、文件规模及演练用时细化工期;当前不把未测量的数据量换算为承诺日期。每个里程碑只处理阻塞目标的问题。
## 外部依据与待核验项
本轮尝试读取 Docker Compose、PostgreSQL 备份与 pgvector 官方文档;网络读取不完整。成功获取 Docker Official Image 的 PostgreSQL 说明(docker-library/docs 的 postgres/README.md),其中确认 PGDATA/卷位置随主版本变化、初始化参数仅对新数据目录生效、密码支持文件注入。这些仅作为镜像适配约束,未确定最终镜像 digest,也未验证某个部署平台。
实施 U2 前需针对所选精确版本再次核对 Compose/镜像/pgvector 官方资料,并做真实冒烟;失败则阻止该镜像采用,不阻止本方案的源码设计工作。
@@ -0,0 +1,61 @@
# 无项目对话(Scratch Session)实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 任意启用账号可创建彼此隔离的通用智能体会话;无 `project.create` 也能用;不得接入知识树/文档树等项目专属功能。
**Architecture:** 每个无项目对话 = 带 `settings.host.kind = "scratch"` 的私有空壳 `Project` + 恰好一条 `Conversation`。新建走专用 API(不要求 `project.create`)。列表/侧栏把 scratch 从真项目切换器剔除,归入虚拟分类「无项目对话」。服务端对 scratch 统一 fail-closed 拒绝 RDM/知识发布/成员等。
**Tech Stack:** Go Backend(store/server/authz)、TypeScript Frontend(store/TaskSidebar)、现有 SQLite projects 表 `settings` JSON。
**Spec:** [docs/superpowers/specs/2026-10-22-scratch-session-general-agent-design.md](../specs/2026-10-22-scratch-session-general-agent-design.md)
**实施执行:** agent-per-unit(配置:AGENT-EXECUTION.md);`fallback_to_host: never`。
## Global Constraints
- 固有能力不可做成 `role_permissions` 行。
- ListProjects 前后端一致过滤 scratch。
- RDM 未部署时也须宿主侧拒绝,不能只藏 UI。
- 最小充分:不做升级、协作、配额、nullable project_id。
## Review Focus
- 无 `project.create` 的创建路径与真项目门禁是否拆开
- scratch 上项目专属能力是否服务端 fail-closed
- 侧栏是否把 scratch 与真项目分开
## 文件映射
| 区域 | 路径 |
|------|------|
| 标记 helper | Backend store 或 core 小 helper |
| 创建 API | `modules/backend/internal/server/server.go` |
| 列表过滤 / 1:1 | `modules/backend/internal/store/projects.go`、server createConversation |
| 门禁 | `project_lifecycle.go`、cmdbus/RDM、knowledge、跨读 |
| 前端 API/store/侧栏 | `api.ts`、`store.ts`、`TaskSidebar.tsx` |
## Tasks
### U1 — Backend 创建与固有授权
- [ ] 实现 `IsScratchProject`;`POST /api/scratch-sessions`
- [ ] ListProjects 排除 scratch;禁止 POST /api/projects 伪造 scratch;scratch 下禁止二开会话
- [ ] 角色 catalog 不暴露可配置 scratch 动作
- [ ] 单测覆盖无/有 project.create、禁用账号、伪造、1:1
### U2 — Backend 项目专属 fail-closed
- [ ] `rejectIfScratch` 挂到 projectWork/成员/RDM/knowledge/X1/跨读
- [ ] 允许消息/附件/改名置顶归档/级联删除
- [ ] 单测正负例
### U3 — Frontend
- [ ] `createScratchSession`;sidebarMode;去掉无项目时自动「默认项目」
- [ ] TaskSidebar「无项目对话」分类与隐藏项目专属入口
- [ ] 前端测试
### U4 — 验证收口
- [ ] 最小相关测试通过;task-sync / PR / CI / 独立 Review
@@ -0,0 +1,64 @@
# 独立 Review:容器化存储 U1(环境与数据归属合同)
- 日期:2026-10-22
- 审阅对象:`feature/container-storage-plan-u1` 分支 HEAD
- base:`955a32de2bb49171348bad5f566cce935df19212`
- head:`e1f08650a12656611f27438f23fddbfab6a96299`(分支尖端;实施内容经 agent-exec transport 提交 `2d75d101a04ce1d1c9fa1d66d9567894222399d5`,另有路由调整 transport 提交 `0c8bea37` 与任务卡记录提交)
- 送审 diff:`/tmp/container-storage-u1-review.diff`(已核对与 `git diff 955a32de..HEAD` 的代码部分逐字一致;仅 `.task/**` 任务卡不在 diff 内,属 host 直写豁免区)
- 方案依据:`docs/superpowers/plans/2026-10-22-container-storage-plan.md` U1 小节、KTD1—KTD4
- 审阅方式:全新会话独立审阅(非实施者);实际读代码核对绑定解析、拒绝路径与身份保留逻辑;未实施任何修复。
## 总体判定:REVIEW PASS
无阻塞项。U1 验收场景(旧配置升级保留身份、错库/错根/foreign marker 拒绝、同名项目或冲突端口修改前失败、另一 checkout 定位同一资源、缺必需绑定不静默新建空库)均有实现且被测试覆盖;KTD3 单一权威合同成立;无秘密入仓;测试 80/80 通过(1 项真实 PG 专项显式 NOT_RUN)。下列非阻塞项建议在 U2 及最终交付前处理。
## 阻塞项
无。
## 非阻塞项
| # | 级别 | 位置 | 发现 | 建议 |
|---|---|---|---|---|
| N1 | 建议 | `scripts/svc.sh` `svc_set_key`/`svc_del_key`(4674/4691 行起) | manifest `set` 因绑定校验或冲突被拒(exit 2/3/7)时,回退路径仍把该键写入派生视图 `svc.env`,整体操作"成功"返回。权威 manifest 未被污染(`resolve_storage_bindings` 只读 manifest),且两种漂移方向在启动时均 fail-closed(`svc.env=container`/`manifest=legacy` → `verify --require-storage` 触发 `完整存储合同要求 storage_mode=container` 拒启;反向漂移由 record_state 重建自愈),但"冲突在修改前失败"的语义被弱化为"权威拒绝+视图静默写入"。当前无任何调用方经 `svc_set_key` 写存储键(调用点仅 autostart/exec/dbref/dsn/conffile/addr/tags),风险处于潜伏状态。 | 存储键(STORAGE_KEYS 集合)在 manifest set 失败时硬失败,不走 legacy 单键回退。 |
| N2 | 建议 | `scripts/svc.sh` `svc_manifest_verify`(1302 行) | container 分支门控用 `svc_get storage_mode`(读派生 `svc.env`)而非 manifest identity。manifest=container 而 svc.env 过期(manifest 与 svc.env 两次原子写之间的崩溃窗口,或绕过 svc 手改 manifest)时,该次启动跳过存储校验;下次 record_state 经 bulk-set 重新派生后自愈。窗口窄且可自愈,但与"manifest 为权威"的门控意图不一致。 | 门控改为读 manifest identity 的 `storage_mode`(或在检测到两处不一致时拒绝并提示重建派生视图)。 |
| N3 | 建议 | `deploy/data-ownership.md`(持久文件根行) | 声明"禁止他实例共用同一根",但 `check_conflicts` 只扫描 compose 项目名与端口,不检测跨实例 files_root 重叠/嵌套;测试夹具(precheck `test_compose_project_conflict_refused`)中 b 的根位于 a 的根之下,同样未拦截。无 U1 验收场景要求此项,属文档强于实现。 | 收紧措辞为"登记层不拦截,容器挂载层(U2)负责隔离",或在 U2 增加跨实例根重叠预检。 |
| N4 | 记录 | `scripts/svc_manifest.py` `check_conflicts` | 冲突扫描范围限定同一 runtime 目录(`RUNTIME="$ROOT/runtime"`,`--runtime` 显式传入)。同机跨 checkout 的多个 runtime 目录之间的 compose 项目名/端口冲突在 U1 登记层不可见,实际碰撞推迟到 U2 容器启动时由端口占用/compose 报错暴露。实现注释与文档均按"同一 runtime"表述,范围自洽;U2 场景"prod/test/verify 同时运行互不冲突"已承接此项。 | U2 落地容器驱动时复核跨 runtime 同机冲突的最终防线。 |
| N5 | 建议 | `tests/` | 两个覆盖缺口:(a) 无 container 模式实例完整 start/stop 生命周期测试——`preserve_storage_keys`(record_state → bulk-set 写回时保住存储键)与 svc.sh 传 `--runtime` 的改动未被端到端 exercised,若该逻辑回归,绑定会在一次生命周期写回后被静默清掉;(b) `test_CS_U1_missing_binding_rejected` 只覆盖"全部缺失",未覆盖"三库中任一缺失"(如 base/rdm 齐备仅 kb 缺失)的逐槽拒绝。 | 各补一条测试:container 实例启停后锚点与 kv 绑定不变;逐槽缺失各断言一次。 |
| N6 | 记录 | `AGENT-EXECUTION.md`(implementation 角色顺序) | 不在 U1 单元 allowed_paths 内,属越界文件变更;但它是独立登记的路由单元(任务卡行 `container-storage-routing-promote-antigravity`,transport `0c8bea37`,收据 `/tmp/container-storage-routing-receipt2.json`),披露完整,且变更注释自述"临时调整,交付前回退"。 | 按其自述在 U1 合入/交付前回退;不阻塞 U1 评审结论。 |
| N7 | 记录 | `scripts/svc_manifest.py` | `slot_port_of` 只校验 `isdigit()`,接受 `0` 与超范围数值,未限 1—65535。 | 顺手项,U2 接真实容器端口发布前收紧即可。 |
| N8 | 记录 | `scripts/svc_manifest.py` `cmd_verify` | container 启动路径把 `resolve_storage_bindings`(源自 manifest)的值回传给 `verify` 比对,本质是 identity↔kv 自一致性检查(可拦手改 manifest 的不一致),并非运行时"连到外宾库"检测;后者按方案属 U2(启动顺序真实认证/身份检查)与 U3(库内身份)范围。U1 的"错库拒绝"在绑定锚点层面成立(`base 库绑定漂移` 路径有测试),范围声明准确。 | 无需改动;U2 接线时保留本检查作为快速失败层。 |
## 核对点(实际读代码/跑测试核对)
1. **旧配置升级保留身份**:`build()` 对已带 storage 键的 kv 只做形态校验(`require_complete=(mode==container)`),不重建 instance_id;`test_CS_U1_legacy_upgrade_keeps_identity` 断言升级后 `instance_id`/`data_root` 不变、锚点落 identity。✅
2. **身份保留/重锚**:`reanchor(write=False)` 预览仅用于冲突扫描、不落 marker;`mutate` 顺序为"校验 → 冲突扫描 → 重锚 → 落盘",冲突时不落盘;`slot_ref_of` 冲突(dbref 与 dsn_ref 同设)、相对路径 `*_dsn_ref`/`*_role_ref`/`files_root` 均拒绝;legacy→container 升级基座可回落既有 `dbref=` 专键,明文 `dsn=` 不进锚点(I5)。✅
3. **拒绝路径**:container 缺任一必需绑定在 set(`validate_storage_kv` exit 3)与 verify(`--require-storage` + `require_complete=True`)双端拒绝;错文件根/错库绑定漂移 exit 6(测试断言"文件根"/"base 库绑定漂移");foreign marker 沿用 base 逻辑(T5b);compose 项目名/端口冲突 exit 7 且发生在落盘前(`check_conflicts` 在 `save` 之前)。✅
4. **不静默新建空库**:U1 层无任何默认绑定生成;`resolve_storage_bindings` 缺绑定 exit 3 → svc.sh 拒绝;manifest 缺失时 verify exit 4、marker 缺失/损坏 exit 5,均阻塞启动。未发现静默回退空库路径。✅
5. **另一 checkout 定位同一资源**:`files_root` 强制绝对路径并 normpath,锚点/解析不依赖 cwd 与仓库根;`test_CS_U1_other_checkout_keeps_absolute_files_root` 与 T6 从外部 cwd 调用验证。✅
6. **调用方环境污染**:`env_config` 重置存储全局并 `unset AGENT_SYSTEM_FILES_ROOT/ENV_CLASS/COMPOSE_PROJECT/BASE_DB_REF/RDM_DB_REF/KB_DB_REF`;`test_caller_storage_env_does_not_reach_serve` 检查 serve 进程 environ 与 serve.env;三条棘轮测试守卫 env_config。✅
7. **KTD3 单一权威**:`resolve_storage_bindings` 内嵌 python 直读 `manifest.json`(不读 svc.env、不读调用方 env);U1 未引入 compose 文件/第二份卷或 DSN 权威;凭据仅存受保护文件路径引用。`svc.env` 仍是派生只读视图(record_state 不产出存储键,`preserve_storage_keys` 自 manifest 旧 kv 保留)。✅
8. **归属清单与契约文档**:`deploy/data-ownership.md` 覆盖基座/RDM/KB/文件根/环境身份/实例身份/Compose 项目七类所有者,含引用形式与唯一写者;体量/源版本/表清单明示"需另行授权访问后补全"(与方案"生产盘点需先获得对应访问授权"及诚实出口一致);无秘密值。`docs/svc.md` §10.1 键表与 `PROJECT.md` 指针已同步;链接目标 `docs/recovery/u1-data-ownership.md`、`docs/recovery/u1-identity-manifest.md` 均存在。✅
9. **测试充分性**:三测试文件合计覆盖全部五条 U1 验收场景 + 调用方隔离 + 原子写崩溃窗口;缺口仅 N5 两项(均非验收场景直接要求)。`PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_svc_identity tests.test_svc_lifecycle_isolation tests.test_svc_precheck` → **80 tests, OK (skipped=1)**,唯一 skip 为真实 PG 专项显式 NOT_RUN(诚实标注)。✅
10. **秘密与越界**:diff 无明文口令/DSN/token(示例 env 仅受保护路径,测试仅 fixture 值 `ref-only`/`x`);changed files 中除 `AGENT-EXECUTION.md`(见 N6,已另行登记)外全部落在 U1 主要文件/allowed_paths 内;`.task/**` 为 host 直写豁免。✅
## 复现命令
```bash
# 1) 送审 diff 范围与实际 HEAD diff 一致(代码部分逐字相同)
git diff 955a32de2bb49171348bad5f566cce935df19212 HEAD \
-- AGENT-EXECUTION.md PROJECT.md deploy docs/superpowers/plans docs/svc.md scripts tests \
| diff - /tmp/container-storage-u1-review.diff && echo IDENTICAL
# 2) 单元测试(本机实测:Ran 80 tests, OK (skipped=1),约 64s)
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest \
tests.test_svc_identity tests.test_svc_lifecycle_isolation tests.test_svc_precheck
# 3) 对象 SHA
git rev-parse HEAD # e1f08650a12656611f27438f23fddbfab6a96299
git rev-parse 955a32de # 955a32de2bb49171348bad5f566cce935df19212
```
## 结论
U1 单元按方案完成且质量合格:绑定/身份权威唯一、拒绝路径完整且 fail-closed、验收场景全部落地并有测试背书。上述 N1—N8 均不阻塞本单元交付;N1/N2/N5 建议随 U2 或合入前收口,N6 的路由临时调整须按其自述在交付前回退。
@@ -1,64 +0,0 @@
# 两棵树后续 A1 增量:阶段 6 评审包
- 任务卡:`.task/feature-rdm-two-trees-followup.md`(initiative,manual:每个增量用户阶段 6 人审后才合入)
- 对象:Dev `feature/rdm-two-trees-followup`,base `8cf4619`(R0 `46e331a` + 建卡),A1 实施提交 `000a2d2`
- 依据:阶段 5 计划 r1 W0 / W3 节;原分支提交 `9873a55`(W0 守护)、`cabac58`(端口修复)
- 证据来源:本文所有通过类结论均为 SELF(作者本地运行与关键字核对),GATE 以 PR HEAD 的 CI 为准
## 1. A1 交付内容
| 项 | 变更 | 说明 |
|---|---|---|
| R1 W0 DHF 清单守护 | 新增 `scripts/dhf_catalog_guard.py`(100755)与 `tests/test_dhf_catalog_guard.py` | 与 `9873a55` 内容逐字节一致(`git diff 9873a55 HEAD` 仅显示脚本 mode 为可执行);守护只校验清单状态,不宣称任何模板 published |
| R2 svc 测试动态端口 | `tests/test_svc.py`、`tests/test_svc_recover.py`、`tests/test_svc_realcombo.py` | `cabac58` 补丁已不能在最新 main 直接应用,worker 按其意图重新实现:每个用例经 `available_port()` 分配互不重复的端口,替换 18791–18800、9471–9474 等固定端口与 `free_fixture_port()`;被删除的 7 处断言均以同一断言改用分配端口的形式保留,无断言删减 |
实施:`agent-exec` cursor worker 第 1 次尝试成功(run `b7a8e78a2b6d0d8f6803490c7fa8af2e`,attempt `9075eee055af`),经 `agent-exec apply` 快进集成,变更路径与单元 `allowed_paths` 一致。
定向验证(SELF,被测 HEAD `000a2d2`):
```text
python3 -m unittest tests.test_dhf_catalog_guard tests.test_svc tests.test_svc_recover tests.test_svc_realcombo
Ran 67 tests ... OK (skipped=16)
```
16 个 skip 为需要 `SVC_REAL_*_BIN` 真实二进制的 realcombo 用例(NOT_RUN),与改动前一致。
## 2. W3 余项差距核对(RDM `origin/main` = `5a27d78`)
计划目标测试名:`TestRecursiveBlock|TestLegacyDocMigration|TestTemplateBinding`。
| 计划验证点 | main 现状 | 依据(关键字级 SELF,需在后续增量精确复核) |
|---|---|---|
| 递归块、稳定 ID、深于五级 | 已覆盖 | `internal/domain/document_blocks_test.go`:`TestRecursiveBlockInstanceHasIndependentIdentityAndReplays`(depth ≥ 6)、`TestRecursiveBlockReplayRejectsForgedOrFutureNodes`(伪造/未知未来节点拒绝) |
| 模板绑定 | 已覆盖 | `internal/rdmapp/read_isolation_boundary_test.go`:`TestTemplateBindingCannotAliasPublishedProjectOrRetainedTransaction` |
| 合并单元格 | 结构层已覆盖 | `document_blocks_test.go` 断言 `ColSpan == 2`;`template_asset_test.go` 拒绝非法 `RowSpan` |
| 旧 Section.ID 维持为章节身份 | 已覆盖 | `TestLegacySectionBecomesDeterministicRecursiveReadWithoutWrites`、`internal/rdmapp/document_blocks_test.go` `TestLegacyDocSectionNodeIDsSurviveStoreRestart` |
| 旧数据库备份副本迁移两次同值(`TestLegacyDocMigration`) | 缺失 | 无该测试名,未检到双次迁移同值用例 |
| 图形不透明块 | 缺失 | 测试中未检到 figure/opaque 块 |
| 移动/改名保持引用 | 缺失 | 测试中未检到 move/rename 用例 |
| 已批准版与旧下载仍可回取 | 缺失 | 未检到对应用例 |
| 重复组/重复表格 | 部分 | 关键字出现在上述两个测试文件,是否覆盖“重复表格”语义待精确复核 |
| 完成条件:真实项目实例表达新项目建议书及用户需求 12 组要求 | 未验证 | 依赖 W11 DHF 资产 published 状态,当前只能验证实例机制 |
结论:W3 主体(递归实例、模板绑定、旧 Section 身份)已在 RDM main;余项集中在旧数据迁移幂等、图形块、移动/改名引用与旧批准版回取。建议作为子任务“W3 余项”在 RDM 仓单独登记实施。
## 3. 剩余项与门禁
| 剩余项 | 门禁 |
|---|---|
| W3 余项(上表缺失 4 项 + 1 项待复核) | 下一增量,RDM 仓 worker 实施 |
| B:W5 追溯网、W7 上下文提供方 | 依赖 W3/W4 稳定 |
| C:W8 RDM 审计投影、W10 正式前端 | W8 接入宿主需 W9;W10 需 W3–W9 合同稳定 |
| D:W11 DHF 全量保真、W12 真实模型全组合验收、W13 收口 | 需资源方案与付费/真实模型授权 |
| X3/R3/F3/V3 产品链 | X3 需 Backend 公开扩展发版,属双钥匙操作,由用户执行 |
## 4. Review 发现(非阻塞)
- `tests/test_svc_realcombo.py` 新增的 `SVC_RCFWEB_ADDR` 环境变量不被 `svc.sh` 读取(伴生 `rcfweb` 由 `deploy --kind static` 自行取端口),属无效但无害配置,仅位于默认 NOT_RUN 的真实 RDM 用例;可在后续增量顺带删除。
- `available_port()` 绑定后即关闭 socket,与 svc.sh 实际绑定之间存在小窗口竞争(docstring 已说明);相较固定端口已消除并行 worktree 间的确定性冲突。
## 5. 请用户阶段 6 判定
- 通过:A1 进入 PR CI(GATE)与合入窗口,按 #518 排队合入。
- 修改:指出需调整项,按新单元重新实施。
- W3 余项是否按第 2 节登记为下一增量。
@@ -1,42 +0,0 @@
# 两棵树后续 W3 余项:阶段 6 评审包
- 状态:**用户阶段 6 通过(2026-10-20)**。确认第 3 节切分:W4 开放结构写入口前必须先补节点种类放置检查。RDM PR [#451](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-RDM/pulls/451) 经用户授权已合入 RDM main(`76b4c05`);本子任务 PR 合入 initiative 分支。
- 日期:2026-10-19;作者:host;子任务分支:Dev `feature/rdm-two-trees-followup-w3-rest`(父 `feature/rdm-two-trees-followup`)、RDM `feature/rdm-two-trees-followup-w3-rest`(base RDM main `5a27d78`)。
- 依据:[阶段 5 计划 W3](../../../modules/documents/docs/records/2026-10-14-rdm-two-trees-latest-templates/rdm-two-trees-stage5-implementation-test-plan-r1.md)(已批准);[A1 评审包第 2 节差距核对](2026-10-19-rdm-two-trees-followup-a1-review.md)。
## 1. 本增量交付
| 差距项(A1 第 2 节) | 交付 | RDM 提交 | 目标测试 |
|---|---|---|---|
| 图形不透明块 | `ContentBlock.Kind` 新增 `figure`:不透明叶子,禁子块/重复/跨格,必须引用带 SHA-256 的附件或 Word 槽位;实例化、重放、`NodesOf` 逐字节保持 | `013b8b1` | `TestRecursiveBlockFigure*` |
| 重复表格(待复核) | 补精确用例:重复行与 v2 重复章节通过校验、实例节点 ID 各异、保留 `Repeat` 与 `RowSpan/ColSpan`、可重放;非法位置拒绝 | `013b8b1` | `TestRecursiveBlockRepeat*` |
| 移动/改名保持引用 | 领域原语 `MoveNode`/`RenameNode`,新事件 `doc.node.move`/`doc.node.rename`:节点 ID、模板块、内容不变;兄弟序连续重排;文档结构修订 +1,乐观并发;拒绝环、陈旧修订、旧章节节点、空标题、只读日志;live 与 replay 共用同一算法;前缀重放可取回更早结构 | `96d53f6` | `TestRecursiveBlockMove*`、`TestRecursiveBlockRename*`、`TestRecursiveBlockMoveRenameNodeRefsSurviveStoreRestart` |
| 旧库备份迁移两次同值;已批准版与旧下载可回取 | 新增 `TestLegacyDocMigration*`:旧形态库(文档/章节/版本/批准)复制为备份副本后两次独立打开与重放,事件行、序号、迁移版本与 `NodesOf` 一致,第二次不写入;未知未来事件类型在启动时写前拒绝;迁移后已批准版与审批提交下载字节/哈希不变(只增测试,未改生产代码) | `f3fa0c0` | `TestLegacyDocMigration*` |
| Dev:删除无效 `SVC_RCFWEB_ADDR` | `tests/test_svc_realcombo.py` 删除 `svc.sh` 从不读取的 `SVC_RCFWEB_ADDR` | Dev `80ac3fa` | `py_compile` |
## 2. 验证
- host 本地验证(SELF)@RDM `f3fa0c0`:`gofmt`、`go vet ./internal/domain/... ./internal/rdmapp`、`git diff --check` 通过;目标选择器 `GOWORK=off go test ./internal/domain ./internal/rdmapp -run 'TestRecursiveBlock|TestLegacyDocMigration|TestTemplateBinding' -count=1` 20 项 PASS;`./internal/domain/... ./internal/rdmapp` 全量 PASS。
- CI(GATE):RDM 草稿 PR [#451](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-RDM/pulls/451) 与本子任务 PR 当前 HEAD 检查(见 PR)。
- 独立 Review(全新会话 code-reviewer,只接收需求、base/head 与 diff;评审者自跑为 SELF):`5a27d78..f3fa0c0` **PASS-with-fixes,无阻塞项**。确认:克隆与摘除无共享切片、校验全过才写入;live 与 replay 共用算法;伪造节点 ID/旧章节/未知 op 被拒;`validateDocNodes` 对既有调用方等价;无新增写入口;测试断言非空。非阻塞项(建议 W4 开放写入口前修):
1. 移动不检查节点种类放置关系(row 可移出 table、cell 可移出 row),`rendererNodeKind` 会照常渲染为表格行;应在 `movedDocNodes` 加放置检查并把相应测试改为预期拒绝——**列为 W4 写入口的前置条件**。
2. `TestLegacyDocMigration` 在 WAL 模式下计算哈希可能漏掉 `-wal` 中的写入。
3. Postgres 用例的“第二次不写库”写入次数硬编码为 0。
4. `figure` 在渲染时退化为空段落(超出本增量需求,导出前须映射或显式报错)。
另:移动/改名事件 `Target` 与载荷 `DocID` 未做一致性校验;重放查找不过滤已删除文档(当前无删除路径)。
## 3. 风险与需人审确认
1. **与 W4 的边界**:移动/改名只提供领域原语,没有 HTTP 路由、插件命令或 rdmapp 写入口;按阶段 5 计划,结构变更须经 W4 结构提案与人工确认后才能调用。W4 开放写入口前须先补节点种类放置检查(独立 Review 非阻塞项 1)。请确认这一切分。
2. **合入冲突**:`internal/domain/template_asset.go`(figure 校验)同时被在途的 project-access-lifecycle 任务修改;本增量只新增一个 case 和一个函数,后合入的一方需要按最新 main 解决冲突。
3. **事件契约新增**:两种新事件对旧二进制属于未知未来事件,旧版本读到时会拒绝启动写入(符合计划的“未知未来事件拒绝”);部署顺序须先升级再产生新事件。
4. **完成条件中的业务项**:“真实项目实例表达新项目建议书及用户需求 12 组要求”依赖 W11 DHF 资产 published,本增量只验证实例机制,不宣称正式标准模板可用。
## 4. 执行记录
| 单元 | CLI/候选 | 终态 | 收据(源码外) |
|---|---|---|---|
| w3-rest-u1-figure-opaque-repeat-table | Cursor attempt 1 | success | /tmp/leftover-20261019b/w3-u1-receipt.json |
| w3-rest-u2-node-move-rename-refs | Cursor attempt 1 | success | /tmp/leftover-20261019b/w3-u2-receipt.json |
| w3-rest-u3-legacy-migration-approved-retrieval | Cursor run 1 `failed-unknown`(host 在其运行期间集成 u1,HEAD 变化);用户授权全新 run 2 | run 2 `blocked`(隔离区缺 `pins/` 无法编译 rdmapp);修正单元包后经用户授权 run 3 success | /tmp/leftover-20261019b/w3-u3-receipt*.json |
| w3-rest-dev-drop-svc-rcfweb-addr | Cursor run 1 `invalid-config`(单元包未就绪)、run 2 `failed-unknown`(Cursor 连接反复失败);用户授权全新 run 3 | success | /tmp/leftover-20261019b/w3-dev-receipt*.json |
@@ -1,33 +0,0 @@
# 两树一网增量 B(W5/W6/W7a)阶段 6 评审包 r1
日期:2026-10-20。状态:独立 Review 通过,**待用户阶段 6 人审**。人审通过前不合入任何分支。
## 1. 范围与结论一览
| 工作项 | 内容 | 交付分支/HEAD | 独立评审 | 修复 |
|---|---|---|---|---|
| W5a 领域核 | 受控边 revision 锁定、候选/确认语义、升版 pending-recheck、AnalyzeImpact/RecomputeImpact 传递闭包(去环/截断/水位/不可变快照)、重放一致 | RDM `feature/rdm-two-trees-followup-w5-w7` @ a7a6df0 | 0 BLOCKING | — |
| W5b 命令面 | rdm.compliance.impact.analyze/get/recompute、rdm.trace.coverage、rdm.trace.pending-recheck | 同上 @ 2e5fcb7 | 0 BLOCKING | — |
| W7a 纯领域 | ProjectContextBundle 组装/失效/渐进证据引用;零基座消费(tag 前约束达成) | 同上 @ 8327603 | 0 BLOCKING | — |
| RDM 评审修复 | 评审 MAJOR M1–M6(copyState/快照 ID 碰撞/端点校验/生产写入路径/空分析/切片别名) | 同上 @ 04991edb | 修复后 domain+rdmapp+plugins 全绿 | ✅ |
| W6 X1 恢复 | 候选 6ba53e4 合并 main 后按批准修订审计补齐(消费点全覆盖/入模门禁/存储去原文边界/负例矩阵) | Backend `feature/w6-x1-source-recheck` @ 8c410b9(draft PR #231) | **7/7 硬需求 PASS,0 BLOCKING 0 MAJOR**(仅 6 MINOR 备忘) | — |
## 2. 证据
- 全部实施经 agent-exec 有界单元(cursor,各 1 次尝试),收据:`~/.cache/agent-system-dev/agent-exec-receipts/{w5a,w5b,w7a,w6-x1-recheck-completion,w5-majors-repair}-*.json`;评审收据同目录。
- SELF:RDM gofmt/vet + domain(0.07s)/rdmapp(79s)/plugins(3.1s) 全绿 @04991edb;Backend build/vet + store/runmgr/workflow/extension 全绿 @8c410b9;backend CI(PR #231)vet-and-test+governance success @8c410b9。
- 评审报告:RDM `docs/reviews/2026-10-20-increment-b-rdm-review.md`(209 行,逐项 PASS/FAIL);Backend `docs/reviews/2026-10-20-w6-x1-recheck-review.md`(235 行,7 硬需求全 PASS)。
- 权威 GATE:合入前各 PR HEAD 的 CI(Backend #231 已绿;Dev/RDM PR 在人审后创建)。
## 3. 已知限制与门禁
- **W7 provider 接线未做**:按计划但书等待 X1 公开 tag(用户双钥匙);当前仅纯领域摘要与测试,不宣布自动注入完成。
- Backend MINOR 6 条(文档/诊断细分/补测建议)已记录于评审报告,不阻塞;建议随 W7 接线一并处理。
- RDM MINOR 14 条见评审报告;不阻塞合入,随增量 C 顺手项处理。
- W6 迁移与 W9 的 v31/v32 顺序:候选分支已在合并 main 后重排,串行合入时按 store 测试为准。
## 4. 请用户阶段 6 裁定
1. W5+W6+W7a 交付是否通过(对应 RDM/Backend 分支保留,人审通过后走各 PR 合入门禁)。
2. X1 公开 tag 是否发布(双钥匙)——通过后 W7 provider 接线另行派发。
3. RDM 评审 MINOR 14 条作为增量 C 顺手项的处理安排。
@@ -0,0 +1,108 @@
# 无项目对话(Scratch Session)设计
- 状态:**已批准**(对话设计第 1–4 节,2026-10-22)。
- 日期:2026-10-22;作者:host(设计整理);来源等级:SELF。
- 配套计划:[实施计划](../plans/2026-10-22-scratch-session-general-agent-plan.md)。
## 1. 目标与非目标
**目标**
- 任意**启用账号**可创建「无项目对话」,当作通用智能体使用(多轮对话)。
- 无 `project.create`(角色「项目访问」)的用户也能完成该路径,不被「默认项目」创建 403 卡住。
- 各无项目对话彼此**独立**:不共享文档、上下文、知识等。
- **不得**接入知识树、文档树、成员、RDM 等项目专属功能。
**非目标(本轮)**
- scratch → 真项目升级 / 事后绑定 RDM
- 分享、协作、邀请进空壳项目
- 配额、TTL、自动清理
- 将 `conversations.project_id` 改为可空等大 schema 重构
## 2. 已锁定决策
| 项 | 决策 |
|---|---|
| 数据模型 | 方案 A:每个无项目对话 = 一个轻量私有空壳 `Project` + 恰好一条 `Conversation` |
| UI | 侧栏虚拟分类「无项目对话」下列会话;**不**进入真项目切换器 |
| 谁可建 | 所有启用账号(有无 `project.create` 均可) |
| 权限 | **固有能力**,不进角色矩阵,**不可被管理员修改**;与可配置的 `project.create` 完全分离 |
## 3. 数据模型与权限
### 3.1 空壳项目约定
- 仍用现有 `projects` 表;`settings` 写入稳定标记:`host.kind = "scratch"`(解析仅经统一 helper,例如 `IsScratchProject`)。
- 每个无项目对话 = **一个 scratch 项目 + 恰好一条会话**(创建时原子写入;禁止在 scratch 下再建第二会话)。
- 项目 `owner` = 当前账号;不建成员、不共享、不邀请。
- 会话默认 `crossPrivate = true`。
### 3.2 权限
| 动作 | 门禁 |
|------|------|
| `POST /api/projects` 创建真项目 | 仍要 `project.create`(不变) |
| 创建无项目对话 | 启用账号即可;**不是**角色可配置动作 |
| scratch 上读写会话/发消息 | 仅 owner |
| scratch 升成真项目 / 绑 RDM | 本轮不做(明确拒绝) |
角色 catalog、`PUT .../permissions`、角色编辑 UI **不得**出现或改写「无项目对话」能力。关掉 `project.create` 不影响 scratch 创建。
### 3.3 专用创建 API
`POST /api/scratch-sessions` → `{ project, conversation }`
- 内部:插 scratch 项目 → 建唯一会话 → **不**触发 RDM provision。
- **禁止**经 `POST /api/projects` 伪造 scratch 标记。
### 3.4 列表语义
- 真项目列表 / 侧栏项目切换器:**过滤掉** scratch。
- 「无项目对话」分类:按会话列出(标题来自会话名)。
## 4. 服务端门禁(禁用项目专属能力)
判定:`IsScratchProject(p)` ≡ `settings.host.kind == "scratch"`。
**一律拒绝(scratch 上)**
- RDM 绑定 / provision;`rdm.*` 命令对 scratch 的 projectId → 403/400
- 知识树 / 文档树 / 法规树 / 驾驶舱 / 模板实例化
- 成员、职责、项目权限矩阵、项目归档协作
- 因「拥有 scratch」而授予 `project.create` 联动能力
- Project Context(X1)注入(视为未启用,走普通通用智能体组装)
- 跨会话读:scratch 会话不可被对端读出,也不可读其他会话
- 在已有 scratch 下再建会话
- 普通创建项目接口伪装 scratch
**允许(通用智能体最小集)**
- 本会话消息、附件(仅挂本 scratch,不进知识发布)
- 会话改名 / 置顶 / 归档 / 删除(删除可级联删空壳项目)
- 普通 LLM 对话;显式 `@knowledge` 对 scratch **关闭**
实现:集中 `rejectIfScratch` / `requireNonScratch`,挂在项目工作、RDM 分发、knowledge、成员 API;不能只藏 UI。
## 5. 前端信息架构
- 项目切换器:真项目列表 + 虚拟项「无项目对话」。
- 选中「无项目对话」:列出本人所有 scratch 会话;「+ 新建对话」调用 scratch API。
- 打开 scratch 会话时:隐藏项目设置、模板生成、依赖当前真项目的 RDM 入口。
- `createTask`:无真项目时**不再**自动 `createProject('默认项目')`;无建项权或用户在无项目对话视图时走 scratch API。
- 无建项权用户主路径:登录 → 默认落在「无项目对话」→ 可直接新建/继续对话。
## 6. 验收标准
1. 无建项权账号可进入「无项目对话」并完成多轮通用对话,不因默认项目 403。
2. 有建项权账号同样可建无项目对话;真项目创建仍只认 `project.create`。
3. 同一用户两个 scratch 会话互不共享附件知识库、上下文条目、跨会话读取。
4. scratch 上 RDM/知识树/文档树/成员/项目设置类能力服务端拒绝;UI 无入口。
5. 角色权限 UI / API 不能出现或改写该能力。
6. scratch 不出现在项目切换器。
7. 无法在已有 scratch 项目下再建第二条会话。
## 7. 最小验证
- Backend:创建门禁、RDM/knowledge 拒绝、ListProjects 过滤、1:1 约束单测。
- Frontend:侧栏分组、无建项权主路径、scratch 视图隐藏项目入口的测试。
+12
View File
@@ -866,9 +866,21 @@ scripts/svc.sh prod deploy --env .env.prod # 等价
| `dbref` | agent 托管数据库绑定 `<pg名>/<库名>`(启动时由实例登记重建 DSN;见 §4.5) |
| `dsn` | agent 外部直连 DSN(受保护;未命中本机实例登记的直连存放于此) |
| `conffile` | agent 配置文件绝对路径(`<name> config` 命令写入,跨重启保留;见 §4.6) |
| `storage_mode` | `legacy`(缺省,SQLite/宿主 PG)/ `container`(三库+文件根合同;见 [deploy/data-ownership.md](../deploy/data-ownership.md)) |
| `env_class` | 环境身份:`prod` / `test` / `verify`(container 必需) |
| `files_root` | 持久文件根**绝对路径**(不相对 checkout 解析;container 必需) |
| `compose_project` | Compose 项目名(同 runtime 唯一;冲突在修改前拒绝) |
| `base_dbref` / `base_dsn_ref` / `base_role_ref` / `base_port` | 基座逻辑库绑定(dsn/role 仅为受保护文件路径引用) |
| `rdm_dbref` / `rdm_dsn_ref` / `rdm_role_ref` / `rdm_port` | RDM 领域库绑定 |
| `kb_dbref` / `kb_dsn_ref` / `kb_role_ref` / `kb_port` | 知识库绑定 |
| `user` / `password` / `dbname` / `authmode` / `pgdata` | postgres 专属 |
| `htdocs` | static 专属 |
容器存储绑定由 `manifest.json` 冻结为身份锚点(与 `data_root`/`db_*` 同级);旧配置
升级保留 `instance_id`。`storage_mode=container` 时缺任一必需绑定、错库、错文件根、
foreign marker、同名 Compose 或端口冲突均拒绝启动/修改,不静默建空库、不回退默认。
示例键见 [`deploy/env-bindings.example.env`](../deploy/env-bindings.example.env)。
### 10.2 history.jsonl 事件
一行一个 JSON:`ts / event / env / kind / source / result / pid / addr / detail`。
+2
View File
@@ -64,6 +64,8 @@ for container in "${containers[@]}"; do
done < <(git -C "$container" ls-tree "$commit" deps/)
done
bash "$root/scripts/check_pin_regression.sh" || fail=1
if [ "$fail" -ne 0 ]; then
exit 1
fi
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# 组合指针不倒退:HEAD 中每个 gitlink(deps/*、modules/*)相对 origin/main
# 只能前进——新 pin 必须以 origin/main 的旧 pin 为祖先。事故原型:PR #682
# 合入了过期分支,其树把五个组件 pin 重置回批次前的值,静默丢掉了 main 上
# batch-1 的组合。
#
# ┌─ 逃生口(须 owner Review)──────────────────────────────────────────────┐
# │ PIN_REGRESSION_SKIP=1 声明这是一次有意的降级:失败改为醒目的 warn 并 │
# │ 退出 0。只用于经组件 owner 审阅的刻意回退,PR │
# │ 描述须写明原因;不得用来绕过过期分支。 │
# └─────────────────────────────────────────────────────────────────────────┘
#
# 其他开关:
# PIN_REGRESSION_NO_FETCH=1 旧 pin 在子仓本地缺失时不尝试 fetch(测试/离线用)。
#
# 在当前工作目录所属仓库的顶层运行。origin/main 本地不可解析(浅检出)时只告警、退出 0。
set -euo pipefail
root=$(git rev-parse --show-toplevel)
cd "$root"
base=origin/main
skip=${PIN_REGRESSION_SKIP:-0}
if ! git rev-parse --verify -q "$base^{commit}" >/dev/null; then
echo "warn: $base is not resolvable locally; pin regression check skipped" >&2
exit 0
fi
fail=0
declare -A main_pins=()
while read -r mode _typ sha path; do
[ "$mode" = "160000" ] || continue
main_pins["$path"]=$sha
done < <(git ls-tree -r "$base")
# 子仓须是独立检出的仓库;未初始化时 git -C 会落到 Dev 自身。
own_repo() {
local path="$1" top
top=$(git -C "$path" rev-parse --show-toplevel 2>/dev/null) || return 1
[ "$top" = "$root/$path" ]
}
has_commit() {
git -C "$1" cat-file -e "$2^{commit}" 2>/dev/null
}
ensure_commit() {
local path="$1" sha="$2"
has_commit "$path" "$sha" && return 0
[ "${PIN_REGRESSION_NO_FETCH:-0}" = "1" ] && return 1
git -C "$path" fetch -q origin "$sha" >/dev/null 2>&1 || true
has_commit "$path" "$sha"
}
regress() {
if [ "$skip" = "1" ]; then
echo "warn: PIN_REGRESSION_SKIP=1 — $* (deliberate downgrade; requires owner review)" >&2
else
echo "pin: $*" >&2
fail=1
fi
}
while read -r mode _typ new path; do
[ "$mode" = "160000" ] || continue
old=${main_pins["$path"]:-}
if [ -z "$old" ]; then
echo "ok $path (new, not on $base)"
continue
fi
if [ "$old" = "$new" ]; then
echo "ok $path"
continue
fi
if ! own_repo "$path"; then
regress "$path is not checked out; cannot verify ${new:0:12} descends from $base ${old:0:12}"
continue
fi
if ! ensure_commit "$path" "$old"; then
regress "$path $base pin $old is missing locally and could not be fetched; cannot verify no regression"
continue
fi
if ! ensure_commit "$path" "$new"; then
regress "$path HEAD pin $new is missing locally and could not be fetched; cannot verify no regression"
continue
fi
if git -C "$path" merge-base --is-ancestor "$old" "$new"; then
echo "ok $path (forward ${old:0:12}..${new:0:12})"
continue
fi
regress "$path moves BACKWARD: $base is ${old:0:12}, HEAD is ${new:0:12} (not a descendant)." \
"This is the PR #682 pattern (a stale branch resetting pins over main): merge $base and re-pin," \
"or set PIN_REGRESSION_SKIP=1 for an owner-reviewed deliberate downgrade"
done < <(git ls-tree -r HEAD)
if [ "$fail" -ne 0 ]; then
exit 1
fi
echo "pin regression ok"
-244
View File
@@ -1,244 +0,0 @@
#!/usr/bin/env python3
"""Compare the DHF inventory, immutable originals and one RDM asset catalog.
This checks provenance and publication metadata. It cannot decide document
meaning or Word rendering fidelity; those require the per-asset review.
"""
import argparse
from collections import Counter
from datetime import datetime
import hashlib
import json
from pathlib import Path
import re
import subprocess
import sys
SOURCE_PREFIX = (".templates", "DHF 文件模板")
STATES = {"needs-review", "blocked", "published", "retired"}
SHA256 = re.compile(r"[0-9a-f]{64}\Z")
COMMIT = re.compile(r"[0-9a-f]{40}\Z")
RFC3339 = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})\Z")
def digest(raw):
return hashlib.sha256(raw).hexdigest()
def source_path(dev_root, source):
path = Path(source)
if (path.is_absolute() or path.parts[:2] != SOURCE_PREFIX
or ".." in path.parts or path.suffix.lower() not in {".doc", ".docx"}):
raise ValueError(f"unsafe or non-Word source path: {source}")
segment = dev_root
for part in path.parts:
segment = segment / part
if segment.is_symlink():
raise ValueError(f"source path symlink: {source}")
absolute = dev_root / path
if not absolute.resolve().is_relative_to(dev_root.resolve()) or absolute.is_symlink():
raise ValueError(f"source path leaves the Dev tree: {source}")
return absolute
def package_digest(root, file_ref):
relative = file_ref.get("path")
expected = file_ref.get("sha256")
if not isinstance(relative, str) or not isinstance(expected, str) or not SHA256.fullmatch(expected):
raise ValueError("published asset file reference missing path or hash")
path = Path(relative)
if path.is_absolute() or ".." in path.parts or path == Path("."):
raise ValueError("published asset file reference is unsafe")
segment = root
for part in path.parts:
segment = segment / part
if segment.is_symlink():
raise ValueError("published asset file symlink")
absolute = root / path
if not absolute.resolve().is_relative_to(root.resolve()) or absolute.is_symlink() or not absolute.is_file():
raise ValueError("published asset file missing or escapes catalog")
if digest(absolute.read_bytes()) != expected:
raise ValueError("published asset file hash mismatch")
return expected
def audit(dev_root, inventory, catalog, expected_count, require_published=False,
catalog_root=None, identity_baseline=None):
records = inventory.get("templates")
assets = catalog.get("assets")
if not isinstance(records, list) or not isinstance(assets, list):
raise ValueError("inventory/templates or catalog/assets is missing")
if len(records) != expected_count or len(assets) != expected_count:
raise ValueError(f"expected {expected_count} logical sources and assets")
by_path = {}
ids = set()
for asset in assets:
provenance = asset.get("sourceProvenance") or {}
path = provenance.get("path")
template_id = asset.get("templateId")
if path in by_path or not isinstance(path, str):
raise ValueError("duplicate or missing provenance path")
if template_id in ids or not isinstance(template_id, str) or not template_id:
raise ValueError("duplicate or missing logical template ID")
by_path[path] = asset
ids.add(template_id)
if identity_baseline is not None:
prior = identity_baseline.get("assets")
if not isinstance(prior, list) or len(prior) != expected_count:
raise ValueError("identity baseline is incomplete")
prior_ids = {}
for asset in prior:
if not isinstance(asset, dict):
raise ValueError("identity baseline has invalid asset")
path = (asset.get("sourceProvenance") or {}).get("path")
if path in prior_ids or not isinstance(path, str):
raise ValueError("identity baseline has duplicate source")
prior_ids[path] = asset.get("templateId")
for path, asset in by_path.items():
if prior_ids.get(path) != asset["templateId"]:
raise ValueError(f"template identity drift: {path}")
seen = set()
states = Counter()
hashes = set()
candidate_differences = []
for record in records:
relative = record.get("source")
if not isinstance(relative, str) or relative in seen or relative not in by_path:
raise ValueError(f"inventory source missing, duplicate or unmapped: {relative}")
seen.add(relative)
asset = by_path[relative]
expected = record.get("sourceSha256")
found = (asset.get("source") or {}).get("sha256")
if not isinstance(expected, str) or not SHA256.fullmatch(expected) or found != expected:
raise ValueError(f"catalog source hash mismatch: {relative}")
path = source_path(dev_root, relative)
if not path.is_file() or digest(path.read_bytes()) != expected:
raise ValueError(f"checkout hash mismatch: {relative}")
provenance = asset["sourceProvenance"]
commit = provenance.get("commit")
if provenance.get("repository") != "Agent-System-Dev" or not isinstance(commit, str) or not COMMIT.fullmatch(commit):
raise ValueError(f"untrusted source provenance: {relative}")
historical = subprocess.run(
["git", "-C", str(dev_root), "show", f"{commit}:{relative}"],
capture_output=True, check=False,
)
if historical.returncode or digest(historical.stdout) != expected:
raise ValueError(f"committed source hash mismatch: {relative}")
if (asset.get("source") or {}).get("format") != path.suffix.lower()[1:]:
raise ValueError(f"catalog format mismatch: {relative}")
applicability = asset.get("applicability") or {}
catalog_types = applicability.get("docTypes", [])
if not isinstance(catalog_types, list):
raise ValueError(f"invalid catalog docTypes: {relative}")
if record.get("proposedDocType") not in catalog_types:
candidate_differences.append({
"path": relative,
"inventoryCandidate": record.get("proposedDocType"),
"catalogCandidates": catalog_types,
"confirmed": bool(applicability.get("confirmed")),
})
state = asset.get("state")
if state not in STATES:
raise ValueError(f"invalid asset state: {relative}")
if state == "published":
review = asset.get("review") or {}
normalized = asset.get("normalizedDocx") or {}
condition = applicability.get("condition")
if (not normalized or not catalog_types or applicability.get("confirmed") is not True
or not isinstance(condition, str) or not condition.strip()
or review.get("decision") != "approved"
or review.get("sourceSha256") != expected
or not asset.get("structure") or not asset.get("render")):
raise ValueError(f"published asset lacks approved binding: {relative}")
if (normalized.get("format") != "docx" or asset["structure"].get("format") != "json"
or asset["render"].get("format") != "docx"):
raise ValueError(f"published asset has invalid derived formats: {relative}")
reviewer = review.get("reviewer")
evidence = review.get("evidence")
reviewed_at = review.get("reviewedAt")
if (not isinstance(reviewer, str) or not reviewer.strip()
or not isinstance(evidence, str) or not evidence.strip()
or not isinstance(reviewed_at, str)):
raise ValueError(f"published asset lacks reviewer evidence: {relative}")
if not RFC3339.fullmatch(reviewed_at):
raise ValueError(f"published asset has invalid review time: {relative}")
try:
at = datetime.fromisoformat(reviewed_at.replace("Z", "+00:00"))
except ValueError as exc:
raise ValueError(f"published asset has invalid review time: {relative}") from exc
if at.tzinfo is None:
raise ValueError(f"published asset has invalid review time: {relative}")
converter = asset.get("converter") or {}
if (asset["source"].get("format") == "doc" or
asset["source"].get("sha256") != normalized.get("sha256")):
if (converter.get("inputSha256") != expected or
converter.get("outputSha256") != normalized.get("sha256") or
any(not isinstance(converter.get(field), str) or not converter[field].strip()
for field in ("name", "version", "configuration"))):
raise ValueError(f"published asset lacks conversion provenance: {relative}")
if any(issue.get("status") != "resolved" for issue in asset.get("issues", [])):
raise ValueError(f"published asset has unresolved issues: {relative}")
if (review.get("structureSha256") != asset["structure"].get("sha256")
or review.get("renderSha256") != asset["render"].get("sha256")
or review.get("normalizedSha256") != normalized.get("sha256")):
raise ValueError(f"published asset review hash mismatch: {relative}")
if catalog_root is not None:
package_digest(catalog_root, asset["source"])
package_digest(catalog_root, normalized)
package_digest(catalog_root, asset["structure"])
package_digest(catalog_root, asset["render"])
states[state] += 1
hashes.add(expected)
if require_published and states.get("published", 0) != expected_count:
raise ValueError(f"{expected_count - states.get('published', 0)} assets not published")
return {
"source": "SELF mechanical provenance check",
"matched": len(seen),
"logicalTemplateIds": len(ids),
"uniqueSourceBytes": len(hashes),
"published": states.get("published", 0),
"states": dict(sorted(states.items())),
"candidateTypeDifferences": candidate_differences,
"identityBaselineVerified": identity_baseline is not None,
"publicationValidation": "NOT_RUN: RDM LoadDHFAssetCatalog and human content/Word review are authoritative",
"limits": "This checks provenance and obvious metadata errors; state counts do not prove published bundles are valid.",
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--dev-root", type=Path, required=True)
parser.add_argument("--inventory", type=Path, required=True)
parser.add_argument("--catalog", type=Path, required=True)
parser.add_argument("--expected-count", type=int, default=44)
parser.add_argument("--identity-baseline", type=Path)
parser.add_argument("--require-published", action="store_true")
args = parser.parse_args()
try:
if args.expected_count < 1:
raise ValueError("expected-count must be positive")
inventory_raw = args.inventory.read_bytes()
catalog_raw = args.catalog.read_bytes()
baseline_raw = args.identity_baseline.read_bytes() if args.identity_baseline else None
result = audit(args.dev_root, json.loads(inventory_raw), json.loads(catalog_raw),
args.expected_count, args.require_published, args.catalog.parent,
json.loads(baseline_raw) if baseline_raw is not None else None)
if args.catalog.read_bytes() != catalog_raw:
raise ValueError("catalog changed during check")
result["catalogSha256"] = digest(catalog_raw)
result["inventorySha256"] = digest(inventory_raw)
if baseline_raw is not None:
result["identityBaselineSha256"] = digest(baseline_raw)
if args.require_published:
raise ValueError("RDM publication validator required for full bundle safety and human fidelity")
except (OSError, ValueError, json.JSONDecodeError) as exc:
print(f"dhf catalog guard: {exc}", file=sys.stderr)
return 1
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+103 -10
View File
@@ -1012,8 +1012,9 @@ record_state() {
chmod 600 "$tmp"
# U1 身份:manifest 治理下的整份提交(bulk-set 走 temp→fsync→rename 原子链,
# revision 递增,svc.env 由 manifest 派生);失败保留旧配置,不产生半写态。
# 存储绑定键(STORAGE_KEYS)由 manifest bulk-set 自旧 kv 保留,避免生命周期写回冲掉。
if manifest_ok; then
if python3 "$MANIFEST_PY" "$ENV_DIR" bulk-set < "$tmp"; then
if python3 "$MANIFEST_PY" "$ENV_DIR" bulk-set --runtime "$RUNTIME" < "$tmp"; then
rm -f "$tmp"
return 0
fi
@@ -1217,18 +1218,103 @@ svc_manifest_migrate() {
c_err "[$ENV_NAME] manifest 迁移失败(保持旧治理):$(printf '%s' "$out" | tail -1)"
return 0
}
# resolve_storage_bindings:从本实例 manifest 权威解析容器存储绑定(不读调用方环境)。
# 结果全局:STORAGE_MODE ENV_CLASS FILES_ROOT COMPOSE_PROJECT
# BASE_DB_REF RDM_DB_REF KB_DB_REF BASE_PORT RDM_PORT KB_PORT
# storage_mode=container 且缺任一必需绑定时返回 1(拒绝静默默认/建空库);
# legacy / 未配置返回 0 且 STORAGE_MODE=legacy。
resolve_storage_bindings() {
STORAGE_MODE=legacy; ENV_CLASS=""; FILES_ROOT=""; COMPOSE_PROJECT=""
BASE_DB_REF=""; RDM_DB_REF=""; KB_DB_REF=""
BASE_PORT=""; RDM_PORT=""; KB_PORT=""
manifest_ok || return 0
local out rc=0
out="$(python3 - "$ENV_DIR" <<'PY' 2>&1
import json, os, sys
env_dir = sys.argv[1]
path = os.path.join(env_dir, "manifest.json")
with open(path, encoding="utf-8") as f:
m = json.load(f)
ident = m.get("identity") or {}
kv = m.get("kv") or {}
mode = (kv.get("storage_mode") or ident.get("storage_mode") or "legacy").strip() or "legacy"
print("MODE=%s" % mode)
print("ENV_CLASS=%s" % (kv.get("env_class") or ident.get("env_class") or ""))
print("FILES_ROOT=%s" % (kv.get("files_root") or ident.get("files_root") or ""))
print("COMPOSE_PROJECT=%s" % (kv.get("compose_project") or ident.get("compose_project") or ""))
print("BASE_DB_REF=%s" % (ident.get("base_db_ref") or ""))
print("RDM_DB_REF=%s" % (ident.get("rdm_db_ref") or ""))
print("KB_DB_REF=%s" % (ident.get("kb_db_ref") or ""))
print("BASE_PORT=%s" % (kv.get("base_port") or ident.get("base_port") or ""))
print("RDM_PORT=%s" % (kv.get("rdm_port") or ident.get("rdm_port") or ""))
print("KB_PORT=%s" % (kv.get("kb_port") or ident.get("kb_port") or ""))
if mode == "container":
missing = []
if (kv.get("env_class") or ident.get("env_class") or "") not in ("prod", "test", "verify"):
missing.append("env_class")
fr = kv.get("files_root") or ident.get("files_root") or ""
if not fr or not os.path.isabs(fr):
missing.append("files_root")
if not (kv.get("compose_project") or ident.get("compose_project") or ""):
missing.append("compose_project")
for slot, key in (("base", "base_db_ref"), ("rdm", "rdm_db_ref"), ("kb", "kb_db_ref")):
if not (ident.get(key) or ""):
missing.append(slot + "_db")
if not (kv.get(slot + "_port") or ident.get(slot + "_port") or ""):
missing.append(slot + "_port")
if not (kv.get(slot + "_role_ref") or ""):
missing.append(slot + "_role_ref")
if missing:
print("MISSING=%s" % ",".join(missing), file=sys.stderr)
sys.exit(3)
PY
)" || rc=$?
if [ "$rc" -ne 0 ]; then
c_err "[$ENV_NAME] 存储绑定不完整或无效,拒绝(不静默新建空库/回退默认):$(printf '%s' "$out" | tail -1)"
return 1
fi
local line
while IFS= read -r line; do
case "$line" in
MODE=*) STORAGE_MODE="${line#MODE=}" ;;
ENV_CLASS=*) ENV_CLASS="${line#ENV_CLASS=}" ;;
FILES_ROOT=*) FILES_ROOT="${line#FILES_ROOT=}" ;;
COMPOSE_PROJECT=*) COMPOSE_PROJECT="${line#COMPOSE_PROJECT=}" ;;
BASE_DB_REF=*) BASE_DB_REF="${line#BASE_DB_REF=}" ;;
RDM_DB_REF=*) RDM_DB_REF="${line#RDM_DB_REF=}" ;;
KB_DB_REF=*) KB_DB_REF="${line#KB_DB_REF=}" ;;
BASE_PORT=*) BASE_PORT="${line#BASE_PORT=}" ;;
RDM_PORT=*) RDM_PORT="${line#RDM_PORT=}" ;;
KB_PORT=*) KB_PORT="${line#KB_PORT=}" ;;
esac
done <<<"$out"
return 0
}
# svc_manifest_verify:启动前身份校验——数据 marker 存在且 instance_id 匹配;
# 生效 --data / 数据库绑定与冻结锚点一致。失败即拒绝启动(KTD1/KTD2,I4)。
# 顺序:文件 marker → DB verify(缺/错即拦)。ensure 只在领养(svc_manifest_migrate)
# 与显式 init——普通启动不建身份,缺行即事件(修复路径:svc init,或换具备
# identity 能力的产物更新时由候选门禁补齐)。
# 顺序:文件 marker →(container)存储绑定完整/无漂移 → DB verify(缺/错即拦)。
# ensure 只在领养(svc_manifest_migrate)与显式 init——普通启动不建身份,缺行即事件。
svc_manifest_verify() {
manifest_ok || return 0
local out
if ! out="$(python3 "$MANIFEST_PY" "$ENV_DIR" verify \
--data "$ENV_DATA" --db-mode "${DB_MODE:-}" --db-ref "${DB_REF:-}" 2>&1)"; then
local out verify_args storage_mode
verify_args=(--data "$ENV_DATA" --db-mode "${DB_MODE:-}" --db-ref "${DB_REF:-}")
storage_mode="$(svc_get storage_mode)"
if [ "$storage_mode" = container ]; then
# 生效值只来自本实例登记(env_config 已剥离调用方污染);缺绑/错根/错库拒绝。
resolve_storage_bindings || return 1
verify_args+=(--require-storage
--files-root "$FILES_ROOT" --env-class "$ENV_CLASS"
--compose-project "$COMPOSE_PROJECT"
--base-db-ref "$BASE_DB_REF" --rdm-db-ref "$RDM_DB_REF" --kb-db-ref "$KB_DB_REF")
python3 "$MANIFEST_PY" "$ENV_DIR" check-conflicts --runtime "$RUNTIME" >/dev/null || {
c_err "[$ENV_NAME] 存储绑定冲突,拒绝启动(Compose 项目名或端口与他实例冲突)"
return 1
}
fi
if ! out="$(python3 "$MANIFEST_PY" "$ENV_DIR" verify "${verify_args[@]}" 2>&1)"; then
c_err "[$ENV_NAME] 身份校验失败,拒绝启动:$(printf '%s' "$out" | tail -1)"
c_err " 数据根/数据库绑定是冻结身份;换根/重绑须显式迁移或登记命令(docs/recovery/u1-identity-manifest.md §5)"
c_err " 数据根/数据库/文件根绑定是冻结身份;换根/重绑须显式迁移或登记命令(docs/recovery/u1-identity-manifest.md §5;容器存储见 deploy/data-ownership.md)"
return 1
fi
# 文件校验通过后再碰库。fresh 允许库尚未建立;established 只验证;
@@ -1704,12 +1790,18 @@ env_config() {
# update --all:dev 成功后 genui/rdm1/rdmcand 端口仍被孤儿占用)。
SVC_DB_MODE=""; SVC_DB_SOURCE=""; SVC_DB_REF=""; SVC_DB_DSN_SAVE=""
SVC_PIDCHECK=""; SVC_EXEC=""; SVC_VERSION=""; SVC_SOURCE=""
STORAGE_MODE=""; ENV_CLASS=""; FILES_ROOT=""; COMPOSE_PROJECT=""
BASE_DB_REF=""; RDM_DB_REF=""; KB_DB_REF=""
BASE_PORT=""; RDM_PORT=""; KB_PORT=""
# U1:身份环境同样按实例重置——上一服务 verify 成功后导出的
# AGENT_SYSTEM_INSTANCE_ID/DBREF 不得串给下一服务(批量 update 单进程逐服务解析;
# 2026-10-12 实况:dev 的身份漏给 rdm1/rdmcand,叠加 RDM 产物无 identity 子命令
# 跳过校验,serve 带外宾身份即被基座门禁拒绝)。serve 只允许拿到本服务 verify
# 写入的身份。
# 容器存储 U1:调用方/上一服务的文件根、环境身份、Compose 与三库引用不得污染本实例。
unset AGENT_SYSTEM_INSTANCE_ID AGENT_SYSTEM_DBREF
unset AGENT_SYSTEM_FILES_ROOT AGENT_SYSTEM_ENV_CLASS AGENT_SYSTEM_COMPOSE_PROJECT
unset AGENT_SYSTEM_BASE_DB_REF AGENT_SYSTEM_RDM_DB_REF AGENT_SYSTEM_KB_DB_REF
local registered=""
[ -f "$ENV_DIR/svc.env" ] && registered="$(svc_kind "$name")"
SVC_KIND="${registered:-agent}"
@@ -4578,10 +4670,11 @@ check_port_available() {
# svc_set_key KEY VALUE:改写当前服务 svc.env 的单键。manifest 治理下经 manifest
# 整份提交(原子 + revision);legacy 路径保持先读后写 + 临时文件原子替换。
# 存储绑定键变更经 --runtime 做 Compose/端口冲突预检(修改前失败)。
svc_set_key() {
local key="$1" val="$2" rest tmp
if manifest_ok; then
python3 "$MANIFEST_PY" "$ENV_DIR" set "$key" "$val" && return 0
python3 "$MANIFEST_PY" "$ENV_DIR" set --runtime "$RUNTIME" "$key" "$val" && return 0
c_warn "[$ENV_NAME] manifest set 失败,回退 legacy 单键写"
fi
[ -f "$ENV_DIR/svc.env" ] || { c_err "svc.env 不存在:$ENV_DIR"; return 1; }
@@ -4598,7 +4691,7 @@ svc_set_key() {
svc_del_key() {
local key="$1"
if manifest_ok; then
python3 "$MANIFEST_PY" "$ENV_DIR" del "$key" && return 0
python3 "$MANIFEST_PY" "$ENV_DIR" del --runtime "$RUNTIME" "$key" && return 0
c_warn "[$ENV_NAME] manifest del 失败,回退 legacy 单键写"
fi
[ -f "$ENV_DIR/svc.env" ] || return 0
+399 -36
View File
@@ -1,16 +1,19 @@
#!/usr/bin/env python3
"""svc 实例身份 manifest(U1):manifest.json 为启动权威,svc.env 为派生只读视图。
设计合同:docs/recovery/u1-identity-manifest.md(已批准 2026-10-12)。
设计合同:docs/recovery/u1-identity-manifest.md(已批准 2026-10-12);
容器化存储扩展:docs/superpowers/plans/2026-10-22-container-storage-plan.md U1/KTD1—KTD4。
落地说明:v1.0 以「identity 锚点(数据根/数据库身份,冻结)+ kv 权威键值映射 +
desired 期望态」承载 KTD1/KTD2 的全部不变量;完整 typed 分区随 U2/U6 演进。
desired 期望态」承载不变量;本文件在同一权威上扩展三库/文件根/环境身份绑定,
不另造第二份登记权威。完整 typed 分区与容器启停属后续单元。
用法: svc_manifest.py ENV_DIR <migrate|init|status|set|del|bulk-set|set-desired|verify> [参数]
用法: svc_manifest.py ENV_DIR <migrate|init|status|set|del|bulk-set|set-desired|verify|check-conflicts> [参数]
不变量(对应设计 §1):
不变量(对应设计 §1 + 容器存储 U1):
I3 原子性 —— 整份 temp→fsync→rename→dirsync,任意时刻只有完整旧版或新版。
I4 阻塞 —— manifest 存在时,数据 marker 缺失/身份不符即拒绝,不新建空系统。
I5 机密 —— 本文件不新增任何机密;kv 沿袭 svc.env 既有内容(DSN 迁出属 U6)。
I5 机密 —— 凭据只存受保护文件路径引用(*_dsn_ref / *_role_ref),不存明文。
"""
import json
import os
@@ -22,7 +25,27 @@ SCHEMA = 1
MANIFEST = "manifest.json"
DERIVED = "svc.env"
MARKER_REL = os.path.join(".agentx", "data-marker.json")
PROTECTED = ("db", "dbref", "dsn", "conffile") # 显式持久化写入可重锚;启动期漂移由 verify 拦截
# 显式持久化写入可重锚;启动期漂移由 verify 拦截。含容器存储绑定键。
PROTECTED = (
"db", "dbref", "dsn", "conffile",
"storage_mode", "env_class", "files_root", "compose_project",
"base_dbref", "base_dsn_ref", "base_role_ref", "base_port",
"rdm_dbref", "rdm_dsn_ref", "rdm_role_ref", "rdm_port",
"kb_dbref", "kb_dsn_ref", "kb_role_ref", "kb_port",
)
# record_state/bulk-set 重建时须保留的存储绑定键(避免被生命周期写回冲掉)。
STORAGE_KEYS = (
"storage_mode", "env_class", "files_root", "compose_project",
"base_dbref", "base_dsn_ref", "base_role_ref", "base_port",
"rdm_dbref", "rdm_dsn_ref", "rdm_role_ref", "rdm_port",
"kb_dbref", "kb_dsn_ref", "kb_role_ref", "kb_port",
)
ENV_CLASSES = ("prod", "test", "verify")
DB_SLOTS = ("base", "rdm", "kb")
STORAGE_MODES = ("legacy", "container")
def die(msg, code=1):
@@ -116,6 +139,144 @@ def db_anchor_of(kv):
return "", ""
def storage_mode_of(kv):
mode = (kv.get("storage_mode") or "").strip() or "legacy"
if mode not in STORAGE_MODES:
die("storage_mode 仅允许 legacy|container,收到:%s" % mode, 2)
return mode
def files_root_of(kv):
"""持久文件根:必须已是绝对路径;绝不相对 checkout/worktree 解析。"""
fr = (kv.get("files_root") or "").strip()
if not fr:
return ""
if not os.path.isabs(fr):
die("files_root 必须是绝对路径(不相对 checkout 解析):%s" % fr, 2)
return os.path.normpath(fr)
def slot_ref_of(kv, slot):
"""逻辑库引用锚点:优先 dbref,否则 dsn_ref(受保护文件路径);明文 dsn 禁止。"""
dbref = (kv.get("%s_dbref" % slot) or "").strip()
dsn_ref = (kv.get("%s_dsn_ref" % slot) or "").strip()
if dbref and dsn_ref:
die("%s 绑定冲突:不可同时设置 %s_dbref 与 %s_dsn_ref" % (slot, slot, slot), 2)
if dbref:
return "dbref:%s" % dbref
if dsn_ref:
if not os.path.isabs(dsn_ref):
die("%s_dsn_ref 必须是绝对路径(秘密文件引用):%s" % (slot, dsn_ref), 2)
return "dsn_ref:%s" % os.path.normpath(dsn_ref)
# 基座可回落既有 dbref/dsn 专键(legacy→container 升级兼容,dsn 明文不进锚点)
if slot == "base":
legacy = (kv.get("dbref") or "").strip()
if legacy:
return "dbref:%s" % legacy
# 明文 dsn= 不是引用;container 模式不得用其充当绑定
return ""
def slot_port_of(kv, slot):
return (kv.get("%s_port" % slot) or "").strip()
def slot_role_ref_of(kv, slot):
role = (kv.get("%s_role_ref" % slot) or "").strip()
if not role:
return ""
if not os.path.isabs(role):
die("%s_role_ref 必须是绝对路径(角色秘密文件引用):%s" % (slot, role), 2)
return os.path.normpath(role)
def validate_storage_kv(kv, require_complete=None):
"""校验存储绑定形态。container 模式缺任一必需绑定即拒绝(不静默默认/建空库)。"""
mode = storage_mode_of(kv)
if require_complete is None:
require_complete = mode == "container"
env_class = (kv.get("env_class") or "").strip()
if env_class and env_class not in ENV_CLASSES:
die("env_class 仅允许 prod|test|verify,收到:%s" % env_class, 2)
files_root = files_root_of(kv) # 相对路径在此失败
compose = (kv.get("compose_project") or "").strip()
if compose and (("/" in compose) or (".." in compose) or (" " in compose)):
die("compose_project 非法(禁止路径分隔/空格):%s" % compose, 2)
for slot in DB_SLOTS:
port = slot_port_of(kv, slot)
if port and not port.isdigit():
die("%s_port 必须是正整端口号,收到:%s" % (slot, port), 2)
slot_ref_of(kv, slot) # 冲突/相对路径检查
slot_role_ref_of(kv, slot)
if not require_complete:
return mode
# container 完整合同
if mode != "container":
die("完整存储合同要求 storage_mode=container", 2)
if env_class not in ENV_CLASSES:
die("container 模式缺少必需绑定 env_class(prod|test|verify)", 3)
if not files_root:
die("container 模式缺少必需绑定 files_root(绝对路径)", 3)
if not compose:
die("container 模式缺少必需绑定 compose_project", 3)
for slot in DB_SLOTS:
ref = slot_ref_of(kv, slot)
port = slot_port_of(kv, slot)
role = slot_role_ref_of(kv, slot)
if not ref:
die("container 模式缺少必需绑定 %s 库(%s_dbref 或 %s_dsn_ref)"
% (slot, slot, slot), 3)
if not port:
die("container 模式缺少必需绑定 %s_port" % slot, 3)
if not role:
die("container 模式缺少必需绑定 %s_role_ref(角色秘密文件路径)" % slot, 3)
return mode
def storage_anchor_of(kv):
"""从 kv 计算冻结的存储身份锚点(写入 identity;legacy 为空串)。"""
mode = storage_mode_of(kv)
if mode == "legacy" and not any(kv.get(k) for k in STORAGE_KEYS if k != "storage_mode"):
return {
"storage_mode": "legacy",
"env_class": "",
"files_root": "",
"compose_project": "",
"base_db_ref": "",
"base_port": "",
"rdm_db_ref": "",
"rdm_port": "",
"kb_db_ref": "",
"kb_port": "",
}
# 非完整 container 时仍记录已显式写入的锚点(供漂移检测);完整校验另走 validate
if mode == "container":
validate_storage_kv(kv, require_complete=True)
else:
validate_storage_kv(kv, require_complete=False)
return {
"storage_mode": mode,
"env_class": (kv.get("env_class") or "").strip(),
"files_root": files_root_of(kv),
"compose_project": (kv.get("compose_project") or "").strip(),
"base_db_ref": slot_ref_of(kv, "base"),
"base_port": slot_port_of(kv, "base"),
"rdm_db_ref": slot_ref_of(kv, "rdm"),
"rdm_port": slot_port_of(kv, "rdm"),
"kb_db_ref": slot_ref_of(kv, "kb"),
"kb_port": slot_port_of(kv, "kb"),
}
def preserve_storage_keys(new_kv, prev_kv):
"""bulk-set/生命周期写回时保留未出现在新写集中的存储绑定键。"""
out = dict(new_kv)
for k in STORAGE_KEYS:
if k not in out and k in prev_kv:
out[k] = prev_kv[k]
return out
def marker_path(data_root):
return os.path.join(data_root, MARKER_REL)
@@ -140,13 +301,18 @@ def read_marker(data_root):
return {"schema": -1}
def reanchor(env_dir, m):
"""按显式持久化写入重算身份锚点;数据根变化时在新根补写 marker(目录存在才写)。"""
def reanchor(env_dir, m, write=True):
"""按显式持久化写入重算身份锚点;数据根变化时在新根补写 marker(目录存在才写)。
write=False 仅预览身份(冲突扫描用),不落 marker。
"""
ident = dict(m["identity"])
ident["data_root"] = data_root_of(m["kv"], env_dir)
ident["db_mode"], ident["db_ref"] = db_anchor_of(m["kv"])
for k, v in storage_anchor_of(m["kv"]).items():
ident[k] = v
if ident != m["identity"]:
if os.path.isdir(ident["data_root"]):
if write and os.path.isdir(ident["data_root"]):
write_marker(ident["data_root"], m["instance_id"])
m["identity"] = ident
return m
@@ -160,6 +326,9 @@ def build(env_dir, kv, allow_create):
if not allow_create:
die("数据根不存在且非显式 init,拒绝建立身份(防新建空系统掩盖丢数据):%s" % data_root, 3)
os.makedirs(data_root, exist_ok=True)
# 旧配置升级:若已带 storage 键则校验;纯 legacy 不强制 container 合同
if storage_mode_of(kv) == "container" or any(kv.get(k) for k in STORAGE_KEYS):
validate_storage_kv(kv, require_complete=(storage_mode_of(kv) == "container"))
existing = read_marker(data_root)
if existing is not None:
if existing.get("instance_id") != kv.get("instance_id"):
@@ -170,6 +339,14 @@ def build(env_dir, kv, allow_create):
instance_id = kv.get("instance_id") or "svc-%s-%s" % (name, secrets.token_hex(6))
write_marker(data_root, instance_id)
db_mode, db_ref = db_anchor_of(kv)
storage = storage_anchor_of(kv)
identity = {
"data_root": data_root,
"db_mode": db_mode,
"db_ref": db_ref,
"db_marker": "fresh",
}
identity.update(storage)
m = {
"schema": SCHEMA,
"revision": 1,
@@ -177,18 +354,114 @@ def build(env_dir, kv, allow_create):
"service_group": name,
"created_at": now_iso(),
"desired": "running" if kv.get("autostart") == "1" else "stopped",
"identity": {
"data_root": data_root,
"db_mode": db_mode,
"db_ref": db_ref,
"db_marker": "fresh",
},
"identity": identity,
"kv": {k: v for k, v in kv.items() if k != "autostart"},
}
save(env_dir, m)
return m
def runtime_dir_of(env_dir):
return os.path.dirname(os.path.abspath(env_dir))
def iter_sibling_manifests(env_dir, runtime_dir=None):
rt = os.path.abspath(runtime_dir) if runtime_dir else runtime_dir_of(env_dir)
self_abs = os.path.abspath(env_dir)
if not os.path.isdir(rt):
return
for name in sorted(os.listdir(rt)):
d = os.path.join(rt, name)
if not os.path.isdir(d) or os.path.abspath(d) == self_abs:
continue
path = os.path.join(d, MANIFEST)
if not os.path.isfile(path):
# 兼容尚未 migrate 的 sibling:读 svc.env 中的存储键
env_path = os.path.join(d, DERIVED)
if not os.path.isfile(env_path):
continue
kv = {}
with open(env_path, encoding="utf-8") as f:
for line in f:
line = line.rstrip("\n")
if line and "=" in line and not line.startswith("#"):
k, v = line.split("=", 1)
kv[k] = v
yield name, {
"instance_id": kv.get("instance_id", ""),
"identity": storage_anchor_of(kv) if any(kv.get(k) for k in STORAGE_KEYS) else {},
"kv": kv,
}
continue
try:
with open(path, encoding="utf-8") as f:
yield name, json.load(f)
except Exception: # noqa: BLE001 —— 损坏 sibling 不阻断本实例冲突扫描语义以外的操作
continue
def binding_ports_from(m_or_kv, is_manifest=True):
if is_manifest:
kv = m_or_kv.get("kv") or {}
ident = m_or_kv.get("identity") or {}
else:
kv = m_or_kv
ident = {}
ports = set()
for slot in DB_SLOTS:
p = (kv.get("%s_port" % slot) or "").strip() or (ident.get("%s_port" % slot) or "").strip()
if p:
ports.add(p)
return ports
def compose_of(m_or_kv, is_manifest=True):
if is_manifest:
kv = m_or_kv.get("kv") or {}
ident = m_or_kv.get("identity") or {}
return (kv.get("compose_project") or ident.get("compose_project") or "").strip()
return (m_or_kv.get("compose_project") or "").strip()
def check_conflicts(env_dir, m, runtime_dir=None):
"""同一 runtime 下 Compose 项目名或存储端口冲突 → 修改前失败。"""
kv = m["kv"]
ident = m.get("identity") or {}
compose = (kv.get("compose_project") or ident.get("compose_project") or "").strip()
ports = binding_ports_from(m, is_manifest=True)
if not compose and not ports:
return
self_name = os.path.basename(os.path.abspath(env_dir))
for name, other in iter_sibling_manifests(env_dir, runtime_dir):
other_compose = compose_of(other, is_manifest=True)
if compose and other_compose and compose == other_compose:
die("Compose 项目名冲突:compose_project=%s 已被实例 %s 占用(修改前拒绝)"
% (compose, name), 7)
other_ports = binding_ports_from(other, is_manifest=True)
clash = ports & other_ports
if clash:
die("存储端口冲突:port=%s 与实例 %s 冲突(修改前拒绝)"
% (",".join(sorted(clash)), name), 7)
# 亦与 sibling 的应用监听 addr 端口冲突(避免与已登记服务抢口)
if ports:
rt = os.path.abspath(runtime_dir) if runtime_dir else runtime_dir_of(env_dir)
for name in sorted(os.listdir(rt)) if os.path.isdir(rt) else []:
if name == self_name:
continue
env_path = os.path.join(rt, name, DERIVED)
if not os.path.isfile(env_path):
continue
with open(env_path, encoding="utf-8") as f:
for line in f:
if line.startswith("addr="):
addr = line[5:].strip()
ap = addr.rsplit(":", 1)[-1] if addr else ""
if ap in ports:
die("存储端口冲突:port=%s 与服务 %s 的 addr=%s 冲突(修改前拒绝)"
% (ap, name, addr), 7)
break
def cmd_migrate(env_dir):
if load(env_dir) is not None:
print("manifest 已存在,幂等跳过")
@@ -221,32 +494,41 @@ def cmd_status(env_dir):
return 0
def mutate(env_dir, fn):
def mutate(env_dir, fn, runtime_dir=None):
m = load(env_dir)
if m is None:
die("manifest 不存在:先 migrate/init", 3)
before = json.dumps(m["identity"], sort_keys=True)
fn(m)
# 存储绑定变更:先形态校验,再冲突扫描,最后重锚——冲突时不落盘
if any(k in m["kv"] for k in STORAGE_KEYS) or storage_mode_of(m["kv"]) == "container":
validate_storage_kv(m["kv"], require_complete=(storage_mode_of(m["kv"]) == "container"))
# 预览重锚后的 identity 以做冲突检测(不落 marker / 不写盘)
preview = json.loads(json.dumps(m))
preview = reanchor(env_dir, preview, write=False)
check_conflicts(env_dir, preview, runtime_dir=runtime_dir)
m["revision"] = int(m["revision"]) + 1
m = reanchor(env_dir, m)
m = reanchor(env_dir, m, write=True)
save(env_dir, m)
if before != json.dumps(m["identity"], sort_keys=True):
print("[manifest] 身份锚点已随显式持久化写入更新(data=%s db=%s/%s)"
% (m["identity"]["data_root"], m["identity"]["db_mode"], m["identity"]["db_ref"]))
print("[manifest] 身份锚点已随显式持久化写入更新(data=%s db=%s/%s storage=%s files=%s)"
% (m["identity"]["data_root"], m["identity"]["db_mode"], m["identity"]["db_ref"],
m["identity"].get("storage_mode", "legacy"),
m["identity"].get("files_root") or "-"))
return 0
def cmd_set(env_dir, key, value):
return mutate(env_dir, lambda m: m["kv"].__setitem__(key, value))
def cmd_set(env_dir, key, value, runtime_dir=None):
return mutate(env_dir, lambda m: m["kv"].__setitem__(key, value), runtime_dir=runtime_dir)
def cmd_del(env_dir, key):
def cmd_del(env_dir, key, runtime_dir=None):
def drop(m):
m["kv"].pop(key, None)
return mutate(env_dir, drop)
return mutate(env_dir, drop, runtime_dir=runtime_dir)
def cmd_bulk_set(env_dir):
def cmd_bulk_set(env_dir, runtime_dir=None):
kv = {}
for line in sys.stdin.read().splitlines():
if line and "=" in line and not line.startswith("#"):
@@ -255,8 +537,8 @@ def cmd_bulk_set(env_dir):
kv.pop("autostart", None) # desired 独占期望态
def replace(m):
m["kv"] = kv
return mutate(env_dir, replace)
m["kv"] = preserve_storage_keys(kv, m["kv"])
return mutate(env_dir, replace, runtime_dir=runtime_dir)
def cmd_set_desired(env_dir, state):
@@ -265,7 +547,20 @@ def cmd_set_desired(env_dir, state):
return mutate(env_dir, lambda m: m.__setitem__("desired", state))
def cmd_verify(env_dir, data_root=None, db_mode=None, db_ref=None):
def cmd_check_conflicts(env_dir, runtime_dir=None):
m = load(env_dir)
if m is None:
die("manifest 不存在:先 migrate/init", 3)
check_conflicts(env_dir, m, runtime_dir=runtime_dir)
print("绑定冲突检查通过 instance_id=%s compose=%s"
% (m["instance_id"], m["identity"].get("compose_project") or "-"))
return 0
def cmd_verify(env_dir, data_root=None, db_mode=None, db_ref=None,
files_root=None, env_class=None, compose_project=None,
base_db_ref=None, rdm_db_ref=None, kb_db_ref=None,
require_storage=False):
m = load(env_dir)
if m is None:
die("manifest 缺失:本实例已进入 manifest 治理,缺失即事件(先 init/人工处置,禁止静默重建)", 4)
@@ -284,14 +579,51 @@ def cmd_verify(env_dir, data_root=None, db_mode=None, db_ref=None):
die("数据库模式漂移:生效 %s ≠ 锚定 %s" % (db_mode, ident["db_mode"]), 6)
if db_mode and ident["db_mode"] and db_ref is not None and (db_ref or "") != ident["db_ref"]:
die("数据库绑定漂移:生效 %s ≠ 锚定 %s(重绑须经显式登记命令)" % (db_ref or "<空>", ident["db_ref"]), 6)
print("身份校验通过 instance_id=%s data=%s db=%s/%s"
% (m["instance_id"], ident["data_root"], ident["db_mode"] or "-", ident["db_ref"] or "-"))
storage_mode = ident.get("storage_mode") or "legacy"
if storage_mode == "container" or require_storage:
# container:缺绑定/错根/错库一律拒绝,不回退默认、不建空库
validate_storage_kv(m["kv"], require_complete=True)
if files_root is not None:
got = os.path.normpath(files_root) if os.path.isabs(files_root) else files_root
if not os.path.isabs(files_root):
die("文件根拒绝相对路径(不相对 checkout 解析):%s" % files_root, 6)
if got != (ident.get("files_root") or ""):
die("文件根漂移:生效 %s ≠ 锚定 %s" % (got, ident.get("files_root")), 6)
if env_class is not None and env_class != (ident.get("env_class") or ""):
die("环境身份漂移:生效 %s ≠ 锚定 %s" % (env_class, ident.get("env_class")), 6)
if compose_project is not None and compose_project != (ident.get("compose_project") or ""):
die("Compose 项目漂移:生效 %s ≠ 锚定 %s" % (compose_project, ident.get("compose_project")), 6)
for slot, got in (("base", base_db_ref), ("rdm", rdm_db_ref), ("kb", kb_db_ref)):
if got is None:
continue
want = ident.get("%s_db_ref" % slot) or ""
if (got or "") != want:
die("%s 库绑定漂移:生效 %s ≠ 锚定 %s(错库拒绝,不静默新建)"
% (slot, got or "<空>", want or "<空>"), 6)
print("身份校验通过 instance_id=%s data=%s db=%s/%s storage=%s env=%s files=%s"
% (m["instance_id"], ident["data_root"], ident["db_mode"] or "-", ident["db_ref"] or "-",
storage_mode, ident.get("env_class") or "-", ident.get("files_root") or "-"))
return 0
def _parse_runtime_flag(args):
runtime_dir = None
out = []
it = iter(args)
for a in it:
if a == "--runtime":
runtime_dir = next(it, None)
else:
out.append(a)
return runtime_dir, out
def main(argv):
if len(argv) < 3:
die("用法: svc_manifest.py ENV_DIR <migrate|init|status|set|del|bulk-set|set-desired|verify> [参数]", 2)
die("用法: svc_manifest.py ENV_DIR <migrate|init|status|set|del|bulk-set|"
"set-desired|verify|check-conflicts> [参数]", 2)
env_dir, cmd = argv[1], argv[2]
args = argv[3:]
if cmd == "migrate":
@@ -300,16 +632,27 @@ def main(argv):
return cmd_init(env_dir)
if cmd == "status":
return cmd_status(env_dir)
if cmd == "set" and len(args) == 2:
return cmd_set(env_dir, args[0], args[1])
if cmd == "del" and len(args) == 1:
return cmd_del(env_dir, args[0])
if cmd == "set":
runtime_dir, args = _parse_runtime_flag(args)
if len(args) == 2:
return cmd_set(env_dir, args[0], args[1], runtime_dir=runtime_dir)
if cmd == "del":
runtime_dir, args = _parse_runtime_flag(args)
if len(args) == 1:
return cmd_del(env_dir, args[0], runtime_dir=runtime_dir)
if cmd == "bulk-set":
return cmd_bulk_set(env_dir)
runtime_dir, args = _parse_runtime_flag(args)
return cmd_bulk_set(env_dir, runtime_dir=runtime_dir)
if cmd == "set-desired" and len(args) == 1:
return cmd_set_desired(env_dir, args[0])
if cmd == "check-conflicts":
runtime_dir, _ = _parse_runtime_flag(args)
return cmd_check_conflicts(env_dir, runtime_dir=runtime_dir)
if cmd == "verify":
data_root = db_mode = db_ref = None
files_root = env_class = compose_project = None
base_db_ref = rdm_db_ref = kb_db_ref = None
require_storage = False
it = iter(args)
for a in it:
if a == "--data":
@@ -318,7 +661,27 @@ def main(argv):
db_mode = next(it, None)
elif a == "--db-ref":
db_ref = next(it, None)
return cmd_verify(env_dir, data_root, db_mode, db_ref)
elif a == "--files-root":
files_root = next(it, None)
elif a == "--env-class":
env_class = next(it, None)
elif a == "--compose-project":
compose_project = next(it, None)
elif a == "--base-db-ref":
base_db_ref = next(it, None)
elif a == "--rdm-db-ref":
rdm_db_ref = next(it, None)
elif a == "--kb-db-ref":
kb_db_ref = next(it, None)
elif a == "--require-storage":
require_storage = True
return cmd_verify(
env_dir, data_root, db_mode, db_ref,
files_root=files_root, env_class=env_class,
compose_project=compose_project,
base_db_ref=base_db_ref, rdm_db_ref=rdm_db_ref, kb_db_ref=kb_db_ref,
require_storage=require_storage,
)
die("未知命令或参数不全:%s %s" % (cmd, " ".join(args)), 2)
+16 -1
View File
@@ -13,6 +13,8 @@
不联网、不需要 token;非 pull_request 事件跳过(退出 0)。
期望目标:child → immediate_target;其余(initiative / standalone / 无卡)→ root_target(无卡即 main)。
local 与 ci 均运行 check_pin_regression.sh(gitlink 相对 origin/main 不得倒退);非零即拒绝,
PIN_REGRESSION_SKIP=1 由该脚本降级为告警(有意降级须 owner Review)。
退出码:0 通过;1 门禁拒绝(PR_CHECK_FAILED);2 用法;6 远端无法核验(REMOTE_UNVERIFIED,fail closed)。
仅使用标准库,复用 task_triage 的 Gitea 客户端与 token 读取。token 不打印、不写入任何产物。
@@ -33,6 +35,7 @@ EXIT_USAGE = 2
PRE_MERGE_CLOSED = "已完成(合入前闭合)"
CHILD_COLUMNS = ("子任务分支", "必需", "状态", "PR")
DONE_STATES = ("已合入", "已延期")
PIN_REGRESSION = Path(__file__).resolve().parent / "check_pin_regression.sh"
def sh(*args):
@@ -216,6 +219,17 @@ def final_problems(api, slug, token, branch, meta, text):
return problems
def pin_regression_problems():
proc = subprocess.run(["bash", str(PIN_REGRESSION)], capture_output=True, text=True,
encoding="utf-8", errors="replace")
for line in (proc.stdout + proc.stderr).splitlines():
print(f"[pin-regression] {line}")
if proc.returncode != 0:
return [f"pin regression gate failed (exit {proc.returncode}): a gitlink moves backward against "
"origin/main or cannot be verified; PIN_REGRESSION_SKIP=1 only for owner-reviewed downgrades"]
return []
def verdict(problems, kind, base, head):
if problems:
for problem in problems:
@@ -264,6 +278,7 @@ def cmd_local(args):
problems.append(f"parent initiative {expected} has no open PR into {meta['root_target']}")
if args.final and kind == "initiative":
problems.extend(final_problems(api, slug, token, branch, meta, text))
problems.extend(pin_regression_problems())
return verdict(problems, kind, expected, head)
@@ -281,7 +296,7 @@ def cmd_ci(_args):
if base != expected:
raise Stop(EXIT_FAILED, f"PR base {base!r} differs from expected {expected!r} "
f"for {meta['kind']} branch {head}")
return verdict([], meta["kind"], base, head)
return verdict(pin_regression_problems(), meta["kind"], base, head)
def main(argv=None):
+187
View File
@@ -0,0 +1,187 @@
"""check_pin_regression.sh:gitlink 相对 origin/main 只能前进(PR #682 事故形态)。
全部离线:临时目录内的普通 Git 仓库充当子仓,origin 只指向本地 bare 仓库。
"""
import os
import shutil
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts" / "check_pin_regression.sh"
PR_CHECK = ROOT / "scripts" / "task_pr_check.py"
COMP = "deps/comp"
class PinRegressionTests(unittest.TestCase):
def setUp(self):
self.tmp = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True)
cfg = self.tmp / "gitconfig"
cfg.write_text("")
self.env = dict(
os.environ,
GIT_CONFIG_GLOBAL=str(cfg),
GIT_CONFIG_NOSYSTEM="1",
GIT_AUTHOR_NAME="t",
GIT_AUTHOR_EMAIL="t@example.invalid",
GIT_COMMITTER_NAME="t",
GIT_COMMITTER_EMAIL="t@example.invalid",
)
for key in ("PIN_REGRESSION_SKIP", "PIN_REGRESSION_NO_FETCH"):
self.env.pop(key, None)
self.dev = self.tmp / "dev"
self.dev.mkdir()
self.git(self.dev, "init", "-q")
# 子仓:A ← B,另有从 A 分叉的 C。
self.comp = self.dev / COMP
self.comp.mkdir(parents=True)
self.git(self.comp, "init", "-q")
self.a = self.commit(self.comp, "a")
self.b = self.commit(self.comp, "b")
self.git(self.comp, "checkout", "-q", "-b", "side", self.a)
self.c = self.commit(self.comp, "c")
def git(self, cwd, *args):
return subprocess.run(
["git", *args], cwd=cwd, env=self.env, check=True, capture_output=True, text=True
).stdout.strip()
def commit(self, repo, text):
(repo / "f").write_text(text)
self.git(repo, "add", "f")
self.git(repo, "commit", "-qm", text)
return self.git(repo, "rev-parse", "HEAD")
def pin(self, sha, path=COMP):
self.git(self.dev, "update-index", "--add", "--cacheinfo", f"160000,{sha},{path}")
self.git(self.dev, "commit", "-qm", f"pin {path} {sha[:7]}")
return self.git(self.dev, "rev-parse", "HEAD")
def compose(self, main_pin, head_pin):
"""origin/main 钉 main_pin,HEAD(PR 分支)钉 head_pin。"""
main = self.pin(main_pin)
self.git(self.dev, "update-ref", "refs/remotes/origin/main", main)
if head_pin != main_pin:
self.pin(head_pin)
def run_check(self, **extra):
return subprocess.run(
["bash", str(SCRIPT)], cwd=self.dev, env=self.env | extra, capture_output=True, text=True
)
def test_same_pin_passes(self):
self.compose(self.a, self.a)
res = self.run_check()
self.assertEqual(res.returncode, 0, res.stderr)
self.assertIn(f"ok {COMP}\n", res.stdout)
self.assertIn("pin regression ok", res.stdout)
def test_forward_pin_passes(self):
self.compose(self.a, self.b)
res = self.run_check()
self.assertEqual(res.returncode, 0, res.stderr)
self.assertIn(f"ok {COMP} (forward", res.stdout)
def test_pr682_stale_branch_carrying_old_pin_fails(self):
self.compose(self.b, self.a)
res = self.run_check()
self.assertEqual(res.returncode, 1, res.stdout)
self.assertIn(f"pin: {COMP} moves BACKWARD", res.stderr)
self.assertIn("PR #682", res.stderr)
self.assertIn("PIN_REGRESSION_SKIP=1", res.stderr)
self.assertNotIn("pin regression ok", res.stdout)
def test_diverged_pin_fails(self):
self.compose(self.b, self.c)
res = self.run_check()
self.assertEqual(res.returncode, 1, res.stdout)
self.assertIn(f"pin: {COMP} moves BACKWARD", res.stderr)
def test_skip_env_downgrades_to_warning(self):
self.compose(self.b, self.a)
res = self.run_check(PIN_REGRESSION_SKIP="1")
self.assertEqual(res.returncode, 0, res.stderr)
self.assertIn("warn: PIN_REGRESSION_SKIP=1", res.stderr)
self.assertIn("requires owner review", res.stderr)
def test_missing_origin_main_warns_and_passes(self):
self.pin(self.a)
res = self.run_check()
self.assertEqual(res.returncode, 0, res.stderr)
self.assertIn("warn: origin/main is not resolvable locally", res.stderr)
def test_new_component_not_on_main_passes(self):
self.compose(self.a, self.a)
other = self.dev / "deps" / "other"
other.mkdir()
self.git(other, "init", "-q")
self.pin(self.commit(other, "o"), "deps/other")
res = self.run_check()
self.assertEqual(res.returncode, 0, res.stderr)
self.assertIn("ok deps/other (new, not on origin/main)", res.stdout)
def test_missing_old_object_with_fetch_disabled_fails(self):
self.compose("1" * 40, self.a)
res = self.run_check(PIN_REGRESSION_NO_FETCH="1")
self.assertEqual(res.returncode, 1, res.stdout)
self.assertIn(f"pin: {COMP} origin/main pin {'1' * 40} is missing locally", res.stderr)
def test_missing_old_object_unfetchable_from_local_origin_fails(self):
bare = self.tmp / "comp-origin.git"
self.git(self.tmp, "init", "-q", "--bare", str(bare))
self.git(self.comp, "remote", "add", "origin", str(bare))
self.compose("2" * 40, self.a)
res = self.run_check()
self.assertEqual(res.returncode, 1, res.stdout)
self.assertIn("could not be fetched", res.stderr)
def test_missing_old_object_is_fetched_from_local_origin(self):
upstream = self.tmp / "upstream"
self.git(self.tmp, "clone", "-q", str(self.comp), str(upstream))
newer = self.commit(upstream, "z")
bare = self.tmp / "comp-origin.git"
self.git(self.tmp, "clone", "-q", "--bare", str(upstream), str(bare))
self.git(self.comp, "remote", "add", "origin", str(bare))
self.compose(newer, self.b)
res = self.run_check()
self.assertEqual(res.returncode, 1, res.stdout)
self.assertNotIn("could not be fetched", res.stderr)
self.assertIn(f"pin: {COMP} moves BACKWARD", res.stderr)
self.git(self.comp, "cat-file", "-e", f"{newer}^{{commit}}")
def test_uninitialized_component_cannot_be_verified(self):
self.compose(self.b, self.a)
shutil.rmtree(self.comp)
self.comp.mkdir()
res = self.run_check()
self.assertEqual(res.returncode, 1, res.stdout)
self.assertIn(f"pin: {COMP} is not checked out", res.stderr)
def run_pr_check_ci(self, **extra):
env = self.env | {"GITHUB_EVENT_NAME": "pull_request", "GITHUB_BASE_REF": "main",
"GITHUB_HEAD_REF": "feature/stale", "TASK_TARGET_BRANCH": "main"} | extra
return subprocess.run([sys.executable, str(PR_CHECK), "ci"], cwd=self.dev, env=env,
capture_output=True, text=True, timeout=60)
def test_task_pr_check_ci_hard_fails_on_regression(self):
self.compose(self.b, self.a)
res = self.run_pr_check_ci()
self.assertEqual(res.returncode, 1, res.stdout + res.stderr)
self.assertIn(f"[pin-regression] pin: {COMP} moves BACKWARD", res.stdout)
self.assertIn("[pr-check] FAIL pin regression gate failed", res.stdout)
self.assertIn("[verdict] PR_CHECK_FAILED", res.stdout)
def test_task_pr_check_ci_skip_env_passes(self):
self.compose(self.b, self.a)
res = self.run_pr_check_ci(PIN_REGRESSION_SKIP="1")
self.assertEqual(res.returncode, 0, res.stdout + res.stderr)
self.assertIn("[pin-regression] warn: PIN_REGRESSION_SKIP=1", res.stdout)
self.assertIn("[verdict] PR_CHECK_OK", res.stdout)
if __name__ == "__main__":
unittest.main()
+17
View File
@@ -8,6 +8,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts" / "check-pins.sh"
REGRESSION = ROOT / "scripts" / "check_pin_regression.sh"
CONTAINERS = ("modules/backend", "deps/agent-native", "deps/agent-squad", "deps/agent-intent")
@@ -29,6 +30,7 @@ class CheckPinsTests(unittest.TestCase):
self.dev = self.tmp / "dev"
(self.dev / "scripts").mkdir(parents=True)
shutil.copy(SCRIPT, self.dev / "scripts" / "check-pins.sh")
shutil.copy(REGRESSION, self.dev / "scripts" / "check_pin_regression.sh")
self.git(self.dev, "init", "-q")
def git(self, cwd, *args):
@@ -112,6 +114,21 @@ class CheckPinsTests(unittest.TestCase):
self.assertEqual(res.returncode, 1)
self.assertIn("has no Dev pin", res.stderr)
def test_pin_regression_against_origin_main_fails(self):
old, new = self.repo("deps/error-code-system", "e1", "e2")
self.build({"error-code-system": old}, {"deps/error-code-system": old})
self.git(self.dev, "commit", "-qm", "pr")
stale = self.git(self.dev, "rev-parse", "HEAD")
self.link(self.dev, "deps/error-code-system", new)
self.git(self.dev, "commit", "-qm", "main")
self.git(self.dev, "update-ref", "refs/remotes/origin/main", "HEAD")
self.git(self.dev, "reset", "-q", stale)
res = self.run_pins()
self.assertEqual(res.returncode, 1, res.stderr)
self.assertIn("ok modules/backend error-code-system", res.stdout)
self.assertIn("pin: deps/error-code-system moves BACKWARD", res.stderr)
self.assertNotIn("pins ok", res.stdout)
if __name__ == "__main__":
unittest.main()
-256
View File
@@ -1,256 +0,0 @@
import hashlib
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from copy import deepcopy
ROOT = Path(__file__).resolve().parents[1]
GUARD = ROOT / "scripts" / "dhf_catalog_guard.py"
class DHFCatalogGuardTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name)
self.dev = self.root / "dev"
self.dev.mkdir()
subprocess.run(["git", "init", "-q", str(self.dev)], check=True)
self.items = []
self.assets = []
for index, value in enumerate((b"source-one", b"source-two"), 1):
path = f".templates/DHF 文件模板/阶段{index}/模板{index}.docx"
absolute = self.dev / path
absolute.parent.mkdir(parents=True)
absolute.write_bytes(value)
digest = hashlib.sha256(value).hexdigest()
self.items.append({
"source": path,
"sourceSha256": digest,
"proposedDocType": f"type-{index}",
})
self.assets.append({
"templateId": f"dhf.logical-{index}",
"version": "1.0.0",
"state": "needs-review",
"source": {"sha256": digest, "format": "docx"},
"sourceProvenance": {"repository": "Agent-System-Dev", "path": path},
"applicability": {"docTypes": [f"type-{index}"], "confirmed": False},
})
subprocess.run(["git", "-C", str(self.dev), "add", ".templates"], check=True)
subprocess.run([
"git", "-C", str(self.dev), "-c", "user.name=Test",
"-c", "user.email=test@example.invalid", "commit", "-qm", "sources",
], check=True)
commit = subprocess.check_output(
["git", "-C", str(self.dev), "rev-parse", "HEAD"], text=True,
).strip()
for asset in self.assets:
asset["sourceProvenance"]["commit"] = commit
self.inventory = self.root / "inventory.json"
self.catalog = self.root / "catalog.json"
self.save()
def save(self):
self.inventory.write_text(json.dumps({"templates": self.items}))
self.catalog.write_text(json.dumps({"schemaVersion": "dhf-assets/v1", "assets": self.assets}))
def run_guard(self, *extra):
return subprocess.run([
sys.executable, str(GUARD), "--dev-root", str(self.dev),
"--inventory", str(self.inventory), "--catalog", str(self.catalog),
"--expected-count", "2", *extra,
], capture_output=True, text=True)
def publish_fixture(self, index=0, *, structure=b'{"blocks":[]}', render=b"rendered-docx"):
asset = self.assets[index]
number = index + 1
source = (self.dev / self.items[index]["source"]).read_bytes()
names = {"source": f"source{number}.docx", "structure": f"structure{number}.json",
"render": f"render{number}.docx"}
for label, raw in [("source", source), ("structure", structure), ("render", render)]:
(self.root / names[label]).write_bytes(raw)
asset["state"] = "published"
asset["applicability"].update({"confirmed": True, "condition": "test scope"})
asset["source"]["path"] = names["source"]
asset["normalizedDocx"] = {"path": names["source"], "format": "docx",
"sha256": hashlib.sha256(source).hexdigest()}
asset["structure"] = {"path": names["structure"], "format": "json",
"sha256": hashlib.sha256(structure).hexdigest()}
asset["render"] = {"path": names["render"], "format": "docx",
"sha256": hashlib.sha256(render).hexdigest()}
asset["review"] = {
"decision": "approved", "reviewer": "reviewer", "reviewedAt": "2026-10-14T00:00:00Z",
"evidence": "review-record", "sourceSha256": self.items[index]["sourceSha256"],
"normalizedSha256": asset["normalizedDocx"]["sha256"],
"structureSha256": asset["structure"]["sha256"],
"renderSha256": asset["render"]["sha256"],
}
return asset
def test_accepts_matching_immutable_sources_but_does_not_call_them_published(self):
result = self.run_guard()
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
report = json.loads(result.stdout)
self.assertEqual(report["matched"], 2)
self.assertEqual(report["states"], {"needs-review": 2})
self.assertEqual(report["published"], 0)
def test_rejects_changed_checkout_even_when_catalog_matches_historical_commit(self):
(self.dev / self.items[0]["source"]).write_bytes(b"changed")
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("checkout hash mismatch", result.stderr)
def test_rejects_original_in_symlinked_parent_directory(self):
source = self.dev / self.items[0]["source"]
original_parent = source.parent
moved_parent = original_parent.parent / "alias-target"
original_parent.rename(moved_parent)
original_parent.symlink_to(moved_parent, target_is_directory=True)
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("source path symlink", result.stderr)
def test_rejects_false_publication(self):
self.assets[0]["state"] = "published"
self.save()
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("published asset lacks", result.stderr)
def test_reports_revised_candidate_type_without_approving_publication(self):
self.assets[0]["applicability"]["docTypes"] = ["unrelated"]
self.save()
result = self.run_guard()
self.assertEqual(result.returncode, 0, result.stderr)
report = json.loads(result.stdout)
self.assertEqual(report["published"], 0)
self.assertEqual(report["candidateTypeDifferences"], [{
"path": self.items[0]["source"],
"inventoryCandidate": "type-1",
"catalogCandidates": ["unrelated"],
"confirmed": False,
}])
def test_publication_gate_does_not_accept_a_complete_inventory_as_delivery(self):
result = self.run_guard("--require-published")
self.assertNotEqual(result.returncode, 0)
self.assertIn("not published", result.stderr)
def test_rejects_published_asset_whose_review_does_not_bind_bundle_bytes(self):
asset = self.publish_fixture()
asset["review"]["structureSha256"] = "0" * 64
self.save()
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("published asset review hash mismatch", result.stderr)
def test_rejects_bundle_file_changed_after_approval(self):
asset = self.publish_fixture()
self.save()
(self.root / asset["render"]["path"]).write_bytes(b"modified-after-review")
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("published asset file hash mismatch", result.stderr)
def test_rejects_published_asset_with_unresolved_fidelity_issue(self):
asset = self.publish_fixture()
asset["issues"] = [{"id": "fidelity", "status": "open"}]
self.save()
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("published asset has unresolved issues", result.stderr)
def test_publication_flag_never_claims_authoritative_rdm_validation(self):
for index in range(2):
self.publish_fixture(index, structure=b"not-a-valid-structure", render=b"not-a-valid-docx")
self.save()
result = self.run_guard("--require-published")
self.assertNotEqual(result.returncode, 0)
self.assertIn("RDM publication validator required", result.stderr)
def test_rejects_swapped_logical_ids_against_fixed_identity_baseline(self):
baseline = self.root / "identity-baseline.json"
baseline.write_text(json.dumps({"assets": deepcopy(self.assets)}))
self.assets[0]["templateId"], self.assets[1]["templateId"] = (
self.assets[1]["templateId"], self.assets[0]["templateId"])
self.save()
result = self.run_guard("--identity-baseline", str(baseline))
self.assertNotEqual(result.returncode, 0)
self.assertIn("template identity drift", result.stderr)
def test_report_binds_exact_catalog_bytes(self):
result = self.run_guard()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(json.loads(result.stdout)["catalogSha256"],
hashlib.sha256(self.catalog.read_bytes()).hexdigest())
def test_rejects_published_package_through_symlink_directory(self):
asset = self.publish_fixture()
real = self.root / "real"
real.mkdir()
for key in ["source", "normalizedDocx", "structure", "render"]:
p = self.root / asset[key]["path"]
(real / p.name).write_bytes(p.read_bytes())
asset[key]["path"] = "alias/" + p.name
(self.root / "alias").symlink_to(real, target_is_directory=True)
self.save()
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("symlink", result.stderr)
def test_rejects_string_false_and_null_applicability(self):
asset = self.publish_fixture()
asset["applicability"]["confirmed"] = "false"
self.save()
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("published asset lacks approved binding", result.stderr)
asset["applicability"]["confirmed"] = True
asset["applicability"]["condition"] = None
self.save()
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("published asset lacks approved binding", result.stderr)
def test_rejects_null_reviewer_and_non_rfc3339_timestamp(self):
asset = self.publish_fixture()
asset["review"]["reviewer"] = None
self.save()
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("published asset lacks reviewer evidence", result.stderr)
asset["review"]["reviewer"] = "reviewer"
asset["review"]["reviewedAt"] = "2026-10-14 00:00:00Z"
self.save()
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("published asset has invalid review time", result.stderr)
def test_rejects_changed_normalized_file_without_named_converter(self):
asset = self.publish_fixture()
normalized = b"changed-normalized-docx"
(self.root / "normalized.docx").write_bytes(normalized)
asset["normalizedDocx"] = {
"path": "normalized.docx", "format": "docx", "sha256": hashlib.sha256(normalized).hexdigest(),
}
asset["review"]["normalizedSha256"] = asset["normalizedDocx"]["sha256"]
asset["converter"] = {
"inputSha256": self.items[0]["sourceSha256"],
"outputSha256": asset["normalizedDocx"]["sha256"],
}
self.save()
result = self.run_guard()
self.assertNotEqual(result.returncode, 0)
self.assertIn("published asset lacks conversion provenance", result.stderr)
if __name__ == "__main__":
unittest.main()
+9 -6
View File
@@ -349,18 +349,21 @@ class SvcDbRecoveryTests(unittest.TestCase):
self.register_pg("pg1", 18892, password="addr-pw-3")
dsn = "postgresql://agent:addr-pw-3@127.0.0.1:18892/agent"
d = self.runtime("dev")
self.seed_agent("dev", 18793,
"--addr 127.0.0.1:18793 --data %s --plugins %s --auth auto"
% (d / "data", d / "plugins"),
with socket.socket() as reservation:
reservation.bind(("127.0.0.1", 0))
port = reservation.getsockname()[1]
self.seed_agent("dev", port,
"--addr 127.0.0.1:%d --data %s --plugins %s --auth auto"
% (port, d / "data", d / "plugins"),
extra_rows=[("dbref", "pg1/agent"), ("db", "pg")])
self.clear_serve_record()
self.svc("dev", "restart", "--addr", "127.0.0.1:18794")
self.svc("dev", "restart", "--addr", "127.0.0.1:%d" % port)
record = self.serve_record()
self.assertIn("AGENT_SYSTEM_DSN=%s" % dsn, record)
self.assertIn("--auth auto", record)
self.assertEqual(self.kv("dev", "dbref"), "pg1/agent")
self.assertEqual(self.kv("dev", "addr"), "127.0.0.1:18794")
self.assertIn("--addr 127.0.0.1:18794", self.kv("dev", "exec"))
self.assertEqual(self.kv("dev", "addr"), "127.0.0.1:%d" % port)
self.assertIn("--addr 127.0.0.1:%d" % port, self.kv("dev", "exec"))
self.assertEqual(self.kv("dev", "db"), "pg")
# ---- 4. 外部直连与配置文件来源:后续无参数启动仍恢复 ----
+7 -1
View File
@@ -10,11 +10,15 @@ import os
import shutil
import sqlite3
import subprocess
import sys
import tempfile
import time
import unittest
from pathlib import Path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _port_alloc import free_test_port # noqa: E402
SOURCE = Path(__file__).resolve().parents[1]
SVC_SCRIPT = SOURCE / "scripts" / "svc.sh"
@@ -67,7 +71,9 @@ class SvcAgentBackupTests(unittest.TestCase):
self.assertEqual(proc.returncode, 0, proc.stdout)
return proc
def register_agent(self, name="t1", port=8899):
def register_agent(self, name="t1", port=None):
if port is None:
port = free_test_port()
d = self.root / "runtime" / name
(d / "data").mkdir(parents=True, exist_ok=True)
(d / "svc.env").write_text(
+193
View File
@@ -500,6 +500,199 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
self.skipTest("真 PG 专项应由隔离套件执行(本桩文件不连真实 PG)")
self.skipTest("真实 PG 身份专项 NOT_RUN:需真实实例(SVC_TEST_PG=1 + AGENT_PG_TEST_DSN)")
# ---- 容器存储 U1:环境与数据归属合同 ----
def _storage_secret_dir(self):
d = self.root / "storage-secrets"
d.mkdir(exist_ok=True)
for slot in ("base", "rdm", "kb"):
(d / ("%s.dsn" % slot)).write_text("ref-only\n", encoding="utf-8")
(d / ("%s.role" % slot)).write_text("ref-only\n", encoding="utf-8")
return d
def _apply_container_bindings(self, name, compose, ports, files_root, env_class="prod"):
"""先写齐绑定键,最后打开 storage_mode=container(缺一即拒)。
经 svc_manifest.py set(与 svc_set_key 同源权威)。"""
sec = self._storage_secret_dir()
items = [
("env_class", env_class),
("files_root", str(files_root)),
("compose_project", compose),
("base_dsn_ref", str(sec / "base.dsn")),
("base_role_ref", str(sec / "base.role")),
("base_port", ports[0]),
("rdm_dsn_ref", str(sec / "rdm.dsn")),
("rdm_role_ref", str(sec / "rdm.role")),
("rdm_port", ports[1]),
("kb_dsn_ref", str(sec / "kb.dsn")),
("kb_role_ref", str(sec / "kb.role")),
("kb_port", ports[2]),
("storage_mode", "container"),
]
man = self.root / "scripts" / "svc_manifest.py"
env_dir = self.runtime(name)
runtime = env_dir.parent
for k, v in items:
proc = subprocess.run(
["python3", str(man), str(env_dir), "set", "--runtime", str(runtime), k, str(v)],
cwd=str(self.root), env=self.env, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
self.assertEqual(proc.returncode, 0, "%s=%s → %s" % (k, v, proc.stdout))
def test_CS_U1_legacy_upgrade_keeps_identity(self):
"""旧配置升级到 container 绑定:instance_id / data_root 保留。"""
self.seed_agent("cs1", 9471)
self.start_ok("cs1")
m0 = self.manifest("cs1")
iid, data = m0["instance_id"], m0["identity"]["data_root"]
self.svc("cs1", "stop")
files = self.root / "files-cs1"
files.mkdir()
self._apply_container_bindings("cs1", "svc-cs1", ("25432", "25433", "25434"), files)
m1 = self.manifest("cs1")
self.assertEqual(m1["instance_id"], iid)
self.assertEqual(m1["identity"]["data_root"], data)
self.assertEqual(m1["identity"]["storage_mode"], "container")
self.assertEqual(m1["identity"]["env_class"], "prod")
self.assertEqual(m1["identity"]["files_root"], str(files))
self.assertTrue(m1["identity"]["base_db_ref"].startswith("dsn_ref:"))
self.assertTrue(m1["identity"]["rdm_db_ref"].startswith("dsn_ref:"))
self.assertTrue(m1["identity"]["kb_db_ref"].startswith("dsn_ref:"))
def test_CS_U1_relative_files_root_rejected(self):
self.seed_agent("cs2", 9472)
self.start_ok("cs2")
self.svc("cs2", "stop")
man = self.root / "scripts" / "svc_manifest.py"
env_dir = self.runtime("cs2")
proc = subprocess.run(
["python3", str(man), str(env_dir), "set", "--runtime", str(env_dir.parent),
"files_root", "relative/not-abs"],
cwd=str(self.root), env=self.env, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
self.assertNotEqual(proc.returncode, 0, proc.stdout)
self.assertIn("绝对路径", proc.stdout)
self.assertNotEqual(self.manifest("cs2")["identity"].get("files_root"),
"relative/not-abs")
def test_CS_U1_missing_binding_rejected(self):
"""container 模式缺任一必需绑定不得开启(不静默默认)。"""
self.seed_agent("cs3", 9473)
self.start_ok("cs3")
self.svc("cs3", "stop")
man = self.root / "scripts" / "svc_manifest.py"
env_dir = self.runtime("cs3")
rt = str(env_dir.parent)
proc = subprocess.run(
["python3", str(man), str(env_dir), "set", "--runtime", rt,
"storage_mode", "container"],
cwd=str(self.root), env=self.env, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
self.assertNotEqual(proc.returncode, 0, proc.stdout)
self.assertIn("缺少必需绑定", proc.stdout)
self.assertNotEqual(self.manifest("cs3")["kv"].get("storage_mode"), "container")
def test_CS_U1_wrong_files_root_and_db_verify_rejected(self):
self.seed_agent("cs4", 9474)
self.start_ok("cs4")
self.svc("cs4", "stop")
files = self.root / "files-cs4"
files.mkdir()
self._apply_container_bindings("cs4", "svc-cs4", ("25532", "25533", "25534"), files)
man = self.root / "scripts" / "svc_manifest.py"
env_dir = self.runtime("cs4")
bad_files = subprocess.run(
["python3", str(man), str(env_dir), "verify", "--require-storage",
"--files-root", str(self.root / "other-files")],
cwd=str(self.root), env=self.env, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
self.assertNotEqual(bad_files.returncode, 0, bad_files.stdout)
self.assertIn("文件根", bad_files.stdout)
bad_db = subprocess.run(
["python3", str(man), str(env_dir), "verify", "--require-storage",
"--files-root", str(files),
"--base-db-ref", "dbref:foreign/x"],
cwd=str(self.root), env=self.env, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
self.assertNotEqual(bad_db.returncode, 0, bad_db.stdout)
self.assertIn("base 库绑定漂移", bad_db.stdout)
def test_CS_U1_compose_and_port_conflict_before_modify(self):
self.seed_agent("cs5a", 9475)
self.seed_agent("cs5b", 9476)
self.start_ok("cs5a")
self.start_ok("cs5b")
self.svc("cs5a", "stop")
self.svc("cs5b", "stop")
files_a = self.root / "files-cs5a"
files_b = self.root / "files-cs5b"
files_a.mkdir()
files_b.mkdir()
self._apply_container_bindings("cs5a", "svc-shared", ("25632", "25633", "25634"), files_a)
man = self.root / "scripts" / "svc_manifest.py"
env_b = self.runtime("cs5b")
rt = str(env_b.parent)
# 同 compose 名:修改前失败
sec = self._storage_secret_dir()
for k, v in (("env_class", "test"), ("files_root", str(files_b)),
("compose_project", "svc-shared")):
proc = subprocess.run(
["python3", str(man), str(env_b), "set", "--runtime", rt, k, v],
cwd=str(self.root), env=self.env, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
if k == "compose_project":
self.assertNotEqual(proc.returncode, 0, proc.stdout)
self.assertIn("Compose 项目名冲突", proc.stdout)
else:
self.assertEqual(proc.returncode, 0, proc.stdout)
# 端口冲突
self.seed_agent("cs5c", 9477)
self.start_ok("cs5c")
self.svc("cs5c", "stop")
files_c = self.root / "files-cs5c"
files_c.mkdir()
env_c = self.runtime("cs5c")
for k, v in (
("env_class", "verify"),
("files_root", str(files_c)),
("compose_project", "svc-cs5c"),
("base_dsn_ref", str(sec / "base.dsn")),
("base_role_ref", str(sec / "base.role")),
("base_port", "25632"),
):
proc = subprocess.run(
["python3", str(man), str(env_c), "set", "--runtime", rt, k, str(v)],
cwd=str(self.root), env=self.env, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
if k == "base_port":
self.assertNotEqual(proc.returncode, 0, proc.stdout)
self.assertIn("端口冲突", proc.stdout)
else:
self.assertEqual(proc.returncode, 0, proc.stdout)
def test_CS_U1_other_checkout_keeps_absolute_files_root(self):
"""另一 cwd/checkout 调用仍定位同一绝对 files_root。"""
self.seed_agent("cs6", 9478)
self.start_ok("cs6")
self.svc("cs6", "stop")
files = self.root / "files-cs6"
files.mkdir()
self._apply_container_bindings("cs6", "svc-cs6", ("25732", "25733", "25734"), files)
iid = self.manifest("cs6")["instance_id"]
fr = self.manifest("cs6")["identity"]["files_root"]
elsewhere = tempfile.mkdtemp(prefix="svc-cs-elsewhere-")
self.addCleanup(shutil.rmtree, elsewhere, True)
man = self.root / "scripts" / "svc_manifest.py"
proc = subprocess.run(
["python3", str(man), str(self.runtime("cs6")), "status"],
cwd=elsewhere, env=self.env, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
self.assertEqual(proc.returncode, 0, proc.stdout)
payload = json.loads(proc.stdout.strip())
self.assertEqual(payload["instance_id"], iid)
self.assertEqual(payload["identity"]["files_root"], fr)
self.assertTrue(os.path.isabs(fr), fr)
class AtomicWriteTests(unittest.TestCase):
"""崩溃窗口单元级:os.replace 失败时原文件必须完好(I3 半写不可能)。"""
+38
View File
@@ -52,3 +52,41 @@ class LifecycleIsolationTests(tsr.SvcAutoRecoverTests):
self.assertEqual(again["identity"]["data_root"], data)
self.assertEqual(again["identity"].get("db_marker"), "established")
self.svc("iso2", "stop")
def test_caller_storage_env_does_not_reach_serve(self):
"""调用方 AGENT_SYSTEM_FILES_ROOT / ENV_CLASS / COMPOSE 不得进入 serve。"""
self.seed_agent("iso3", 9463)
env = dict(self.env)
env["AGENT_SYSTEM_FILES_ROOT"] = "/tmp/leak-files-root"
env["AGENT_SYSTEM_ENV_CLASS"] = "prod"
env["AGENT_SYSTEM_COMPOSE_PROJECT"] = "leak-compose"
env["AGENT_SYSTEM_BASE_DB_REF"] = "dbref:leak/base"
env["AGENT_SYSTEM_RDM_DB_REF"] = "dbref:leak/rdm"
env["AGENT_SYSTEM_KB_DB_REF"] = "dbref:leak/kb"
self.svc("iso3", "start", env=env)
pid = self.pid_of("iso3")
self.assertIsNotNone(pid)
raw = Path("/proc/%d/environ" % pid).read_bytes()
self.assertNotIn(b"/tmp/leak-files-root", raw)
self.assertNotIn(b"leak-compose", raw)
self.assertNotIn(b"dbref:leak/base", raw)
self.assertNotIn(b"dbref:leak/rdm", raw)
self.assertNotIn(b"dbref:leak/kb", raw)
serve_env = self.runtime("iso3") / "serve.env"
self.assertTrue(serve_env.is_file())
text = serve_env.read_text()
self.assertNotIn("AGENT_SYSTEM_FILES_ROOT", text)
self.assertNotIn("leak-files-root", text)
self.assertNotIn("leak-compose", text)
self.svc("iso3", "stop")
def test_env_config_resets_storage_globals(self):
"""棘轮:env_config 必须清空存储绑定全局与调用方身份环境。"""
import subprocess
out = subprocess.run(
["bash", "-c",
'source "$1"; declare -f env_config | grep -c \'AGENT_SYSTEM_FILES_ROOT\'',
"x", str(SOURCE / "scripts" / "svc.sh")],
capture_output=True, text=True, cwd=str(SOURCE), timeout=30)
self.assertEqual(out.returncode, 0, out.stderr)
self.assertGreaterEqual(int(out.stdout.strip()), 1)
+192
View File
@@ -246,6 +246,198 @@ class ResolverStateIsolation(unittest.TestCase):
)
self.assertEqual(out.strip(), "1")
def test_env_config_resets_storage_identity_env(self):
# 棘轮:env_config 必须剥离调用方 FILES_ROOT / ENV_CLASS / COMPOSE(容器存储 U1)。
out = _bash(
'source "$1"; declare -f env_config | '
'grep -c \'unset AGENT_SYSTEM_FILES_ROOT AGENT_SYSTEM_ENV_CLASS AGENT_SYSTEM_COMPOSE_PROJECT\''
)
self.assertEqual(out.strip(), "1")
class StorageBindingsPrecheck(unittest.TestCase):
"""容器存储 U1:resolve_storage_bindings 与冲突预检(不启真实服务)。"""
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.rt = Path(self._tmp.name) / "runtime"
self.rt.mkdir()
self.sec = Path(self._tmp.name) / "secrets"
self.sec.mkdir()
self.files = Path(self._tmp.name) / "files"
self.files.mkdir()
for slot in ("base", "rdm", "kb"):
(self.sec / ("%s.dsn" % slot)).write_text("x\n")
(self.sec / ("%s.role" % slot)).write_text("y\n")
# 复制 manifest 脚本到可读路径(与仓内一致)
self.man = REPO / "scripts" / "svc_manifest.py"
def tearDown(self):
self._tmp.cleanup()
def _init_agent(self, name):
env = self.rt / name
data = env / "data"
data.mkdir(parents=True)
(env / "svc.env").write_text(
"kind=agent\nenv=%s\naddr=127.0.0.1:18080\n"
"exec=--addr 127.0.0.1:18080 --data %s\n" % (name, data),
encoding="utf-8",
)
proc = subprocess.run(
["python3", str(self.man), str(env), "migrate"],
capture_output=True, text=True, cwd=str(REPO), timeout=30)
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
return env
def _set(self, env, key, value):
proc = subprocess.run(
["python3", str(self.man), str(env), "set",
"--runtime", str(self.rt), key, str(value)],
capture_output=True, text=True, cwd=str(REPO), timeout=30)
return proc
def test_resolve_storage_bindings_legacy_ok(self):
env = self._init_agent("leg")
out = _bash(
'source "$1"; shift; RUNTIME="$1"; ENV_NAME=leg; ENV_DIR="$2"; '
'if resolve_storage_bindings; then echo rc=ok; else echo rc=fail; fi; '
'printf "MODE=%s\\n" "$STORAGE_MODE"',
str(self.rt), str(env),
)
self.assertIn("rc=ok", out)
self.assertIn("MODE=legacy", out)
def test_resolve_incomplete_container_refuses(self):
env = self._init_agent("inc")
# 只写 storage_mode 会被 manifest 拒绝;直接改 kv 模拟半套后 resolve 拒绝
proc = self._set(env, "env_class", "prod")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
# 手工把 storage_mode 写成 container(绕过 mutate 完整校验)以测 resolve
import json
mp = env / "manifest.json"
m = json.loads(mp.read_text(encoding="utf-8"))
m["kv"]["storage_mode"] = "container"
m["identity"]["storage_mode"] = "container"
mp.write_text(json.dumps(m, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
(env / "svc.env").write_text(
(env / "svc.env").read_text(encoding="utf-8") + "storage_mode=container\n",
encoding="utf-8",
)
try:
_bash(
'source "$1"; shift; RUNTIME="$1"; ENV_NAME=inc; ENV_DIR="$2"; '
'resolve_storage_bindings',
str(self.rt), str(env),
)
self.fail("incomplete container must refuse")
except AssertionError as e:
self.assertIn("存储绑定不完整", str(e))
def test_relative_files_root_refused_on_set(self):
env = self._init_agent("rel")
proc = self._set(env, "files_root", "not/absolute")
self.assertNotEqual(proc.returncode, 0)
self.assertIn("绝对路径", proc.stdout + proc.stderr)
def test_compose_project_conflict_refused(self):
a = self._init_agent("ca")
b = self._init_agent("cb")
for k, v in (
("env_class", "prod"),
("files_root", str(self.files)),
("compose_project", "same-proj"),
("base_dsn_ref", str(self.sec / "base.dsn")),
("base_role_ref", str(self.sec / "base.role")),
("base_port", "26001"),
("rdm_dsn_ref", str(self.sec / "rdm.dsn")),
("rdm_role_ref", str(self.sec / "rdm.role")),
("rdm_port", "26002"),
("kb_dsn_ref", str(self.sec / "kb.dsn")),
("kb_role_ref", str(self.sec / "kb.role")),
("kb_port", "26003"),
("storage_mode", "container"),
):
proc = self._set(a, k, v)
self.assertEqual(proc.returncode, 0, "%s: %s" % (k, proc.stdout + proc.stderr))
(self.files / "b").mkdir(exist_ok=True)
proc = self._set(b, "env_class", "test")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
proc = self._set(b, "files_root", str(self.files / "b"))
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
proc = self._set(b, "compose_project", "same-proj")
self.assertNotEqual(proc.returncode, 0)
self.assertIn("Compose 项目名冲突", proc.stdout + proc.stderr)
def test_storage_port_conflict_refused(self):
a = self._init_agent("pa")
b = self._init_agent("pb")
for k, v in (
("env_class", "prod"),
("files_root", str(self.files / "pa")),
("compose_project", "proj-pa"),
("base_dsn_ref", str(self.sec / "base.dsn")),
("base_role_ref", str(self.sec / "base.role")),
("base_port", "26101"),
("rdm_dsn_ref", str(self.sec / "rdm.dsn")),
("rdm_role_ref", str(self.sec / "rdm.role")),
("rdm_port", "26102"),
("kb_dsn_ref", str(self.sec / "kb.dsn")),
("kb_role_ref", str(self.sec / "kb.role")),
("kb_port", "26103"),
("storage_mode", "container"),
):
(self.files / "pa").mkdir(exist_ok=True)
proc = self._set(a, k, v)
self.assertEqual(proc.returncode, 0, "%s: %s" % (k, proc.stdout + proc.stderr))
(self.files / "pb").mkdir(exist_ok=True)
for k, v in (
("env_class", "test"),
("files_root", str(self.files / "pb")),
("compose_project", "proj-pb"),
("base_dsn_ref", str(self.sec / "base.dsn")),
("base_role_ref", str(self.sec / "base.role")),
("base_port", "26101"),
):
proc = self._set(b, k, v)
if k == "base_port":
self.assertNotEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertIn("端口冲突", proc.stdout + proc.stderr)
else:
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
def test_resolve_complete_container_ok(self):
env = self._init_agent("okc")
(self.files / "okc").mkdir(exist_ok=True)
for k, v in (
("env_class", "verify"),
("files_root", str(self.files / "okc")),
("compose_project", "proj-okc"),
("base_dsn_ref", str(self.sec / "base.dsn")),
("base_role_ref", str(self.sec / "base.role")),
("base_port", "26201"),
("rdm_dsn_ref", str(self.sec / "rdm.dsn")),
("rdm_role_ref", str(self.sec / "rdm.role")),
("rdm_port", "26202"),
("kb_dsn_ref", str(self.sec / "kb.dsn")),
("kb_role_ref", str(self.sec / "kb.role")),
("kb_port", "26203"),
("storage_mode", "container"),
):
proc = self._set(env, k, v)
self.assertEqual(proc.returncode, 0, "%s: %s" % (k, proc.stdout + proc.stderr))
out = _bash(
'source "$1"; shift; RUNTIME="$1"; ENV_NAME=okc; ENV_DIR="$2"; '
'if resolve_storage_bindings; then echo rc=ok; else echo rc=fail; fi; '
'printf "MODE=%s\\nENV=%s\\nFILES=%s\\nCOMPOSE=%s\\n" '
'"$STORAGE_MODE" "$ENV_CLASS" "$FILES_ROOT" "$COMPOSE_PROJECT"',
str(self.rt), str(env),
)
self.assertIn("rc=ok", out)
self.assertIn("MODE=container", out)
self.assertIn("ENV=verify", out)
self.assertIn("COMPOSE=proj-okc", out)
if __name__ == "__main__":
unittest.main()