@warpgogol/forge 2.8.0 → 2.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/README.md +23 -13
- package/README.uk.md +19 -12
- package/dist/os/session/handlers/validate.d.ts.map +1 -1
- package/dist/os/session/handlers/validate.js +23 -1
- package/dist/os/session/handlers/validate.js.map +1 -1
- package/dist/os/session/types.d.ts +31 -1
- package/dist/os/session/types.d.ts.map +1 -1
- package/dist/os/session/types.js +7 -0
- package/dist/os/session/types.js.map +1 -1
- package/os/adr/adr.module.ts +158 -0
- package/os/adr/frontmatter-io.ts +80 -0
- package/os/adr/handlers/archive.ts +244 -0
- package/os/adr/handlers/implement-stamp.ts +331 -0
- package/os/adr/handlers/list-create.ts +207 -0
- package/os/adr/handlers/validate.test.ts +232 -0
- package/os/adr/handlers/validate.ts +429 -0
- package/os/adr/index.ts +33 -0
- package/os/adr/types.ts +141 -0
- package/os/audit/audit.module.ts +48 -0
- package/os/audit/frontmatter-io.ts +84 -0
- package/os/audit/handlers/archive.ts +239 -0
- package/os/audit/index.ts +23 -0
- package/os/audit/types.ts +29 -0
- package/os/compass/compass.module.ts +186 -0
- package/os/compass/handlers/compass-audit-handler.ts +382 -0
- package/os/compass/handlers/compass-change-summary-handler.ts +272 -0
- package/os/compass/handlers/compass-inventory-handler.ts +294 -0
- package/os/compass/handlers/compass-inventory.ts +521 -0
- package/os/compass/handlers/git-revision.ts +137 -0
- package/os/compass/handlers/resolve-scan-root-workpiece.test.ts +65 -0
- package/os/compass/handlers/resolve-scan-root.ts +79 -0
- package/os/compass/index.ts +33 -0
- package/os/core/core.module.ts +948 -0
- package/os/core/handlers/assets-check.ts +122 -0
- package/os/core/handlers/assets-helpers.ts +174 -0
- package/os/core/handlers/assets-list.ts +99 -0
- package/os/core/handlers/build.ts +155 -0
- package/os/core/handlers/determinism-check.ts +354 -0
- package/os/core/handlers/dev.ts +127 -0
- package/os/core/handlers/invariant-engine.test.ts +681 -0
- package/os/core/handlers/knowledge-compact.ts +248 -0
- package/os/core/handlers/lifecycle-handlers.test.ts +524 -0
- package/os/core/handlers/note-frontmatter-validate.test.ts +93 -0
- package/os/core/handlers/note-link-validate.test.ts +117 -0
- package/os/core/handlers/note-orphan-detect.test.ts +94 -0
- package/os/core/handlers/package-health.test.ts +226 -0
- package/os/core/handlers/package-health.ts +231 -0
- package/os/core/handlers/pinned-check.ts +200 -0
- package/os/core/handlers/pinned-init.ts +333 -0
- package/os/core/handlers/pinned-types.ts +50 -0
- package/os/core/handlers/pinned-validate.ts +301 -0
- package/os/core/handlers/profile-resolve.ts +92 -0
- package/os/core/handlers/release-prepare.ts +294 -0
- package/os/core/handlers/release-publish.ts +211 -0
- package/os/core/handlers/validate.ts +262 -0
- package/os/core/handlers/workspace-deps.ts +55 -0
- package/os/core/index.ts +13 -0
- package/os/exploration/exploration.module.ts +90 -0
- package/os/exploration/frontmatter-io.ts +77 -0
- package/os/exploration/handlers/archive.ts +159 -0
- package/os/exploration/handlers/list.ts +77 -0
- package/os/exploration/handlers/show.ts +107 -0
- package/os/exploration/index.ts +25 -0
- package/os/exploration/types.ts +65 -0
- package/os/mission/handlers/archive.test.ts +391 -0
- package/os/mission/handlers/archive.ts +453 -0
- package/os/mission/index.ts +1 -0
- package/os/mission/mission.module.ts +48 -0
- package/os/mission/types.ts +41 -0
- package/os/naming/index.ts +14 -0
- package/os/naming/naming-convention.test.ts +163 -0
- package/os/naming/naming-convention.ts +378 -0
- package/os/naming/naming.module.ts +36 -0
- package/os/notes/index.ts +10 -0
- package/os/notes/notes.module.ts +109 -0
- package/os/plan/frontmatter-io.ts +82 -0
- package/os/plan/handlers/archive.ts +238 -0
- package/os/plan/index.ts +23 -0
- package/os/plan/plan.module.ts +48 -0
- package/os/plan/types.ts +29 -0
- package/os/program/discovery.ts +294 -0
- package/os/program/handlers/complete.ts +419 -0
- package/os/program/handlers/lease.ts +433 -0
- package/os/program/handlers/seal.ts +332 -0
- package/os/program/handlers/validate.ts +213 -0
- package/os/program/lease.ts +156 -0
- package/os/program/program.module.ts +212 -0
- package/os/program/schemas.ts +218 -0
- package/os/program/state.ts +264 -0
- package/os/rfc/acceptance.ts +344 -0
- package/os/rfc/decision-log.ts +310 -0
- package/os/rfc/dna-trace.ts +280 -0
- package/os/rfc/frontmatter-io.test.ts +132 -0
- package/os/rfc/frontmatter-io.ts +130 -0
- package/os/rfc/handlers/archive.ts +300 -0
- package/os/rfc/handlers/check.ts +238 -0
- package/os/rfc/handlers/implement-stamp.ts +515 -0
- package/os/rfc/handlers/index-graph.ts +237 -0
- package/os/rfc/handlers/lifecycle.test.ts +127 -0
- package/os/rfc/handlers/lifecycle.ts +249 -0
- package/os/rfc/handlers/list-create.ts +296 -0
- package/os/rfc/handlers/pipeline-status.ts +173 -0
- package/os/rfc/handlers/shared.ts +121 -0
- package/os/rfc/handlers/supersede-propose.ts +241 -0
- package/os/rfc/handlers/validate-rules.test.ts +891 -0
- package/os/rfc/handlers/validate-rules.ts +984 -0
- package/os/rfc/handlers/validate.ts +166 -0
- package/os/rfc/handlers.ts +19 -0
- package/os/rfc/index.ts +97 -0
- package/os/rfc/rfc.module.ts +416 -0
- package/os/rfc/types.ts +596 -0
- package/os/rfc/verification-evidence.ts +244 -0
- package/os/session/atif-parser.ts +166 -0
- package/os/session/frontmatter-io.ts +163 -0
- package/os/session/handlers/archive.ts +288 -0
- package/os/session/handlers/list.ts +174 -0
- package/os/session/handlers/save.ts +348 -0
- package/os/session/handlers/validate.ts +250 -0
- package/os/session/index.ts +50 -0
- package/os/session/session.module.ts +126 -0
- package/os/session/types.ts +203 -0
- package/os/spec/live-spec-list-show-validate.test.ts +257 -0
- package/os/spec/live-spec-list.ts +78 -0
- package/os/spec/live-spec-merge.test.ts +261 -0
- package/os/spec/live-spec-merge.ts +413 -0
- package/os/spec/live-spec-show.ts +117 -0
- package/os/spec/live-spec-types.ts +93 -0
- package/os/spec/live-spec-validate.ts +170 -0
- package/os/spec/spec-materialize.ts +390 -0
- package/os/spec/spec-schema.ts +174 -0
- package/os/spec/spec-status.ts +284 -0
- package/os/spec/spec-validate.ts +513 -0
- package/os/spec/spec.module.ts +135 -0
- package/os/werkstatt/handlers/lock.ts +168 -0
- package/os/werkstatt/handlers/schema.ts +47 -0
- package/os/werkstatt/handlers/werkstatt-lock-recover.ts +175 -0
- package/os/werkstatt/handlers/werkstatt-lock-status.ts +73 -0
- package/os/werkstatt/handlers/werkstatt-operation-validate.ts +104 -0
- package/os/werkstatt/index.ts +39 -0
- package/os/werkstatt/werkstatt.module.ts +62 -0
- package/os/workflow/handlers.ts +304 -0
- package/os/workflow/index.ts +28 -0
- package/os/workflow/types.ts +99 -0
- package/os/workflow/workflow.module.ts +54 -0
- package/package.json +3 -3
- package/profiles/godot-csharp.yaml +4 -4
- package/profiles/knowledge-typescript-turborepo.yaml +110 -0
- package/profiles/phaser-turborepo.yaml +5 -5
- package/skills/_shared/fo-session-summary.md +132 -16
- package/skills/fo/fo-add-tests/pbt-guide.md +13 -13
- package/skills/fo/fo-doc-audit/SKILL.md +6 -6
- package/skills/fo/fo-handoff/SKILL.md +5 -0
- package/skills/fo/fo-idea/SKILL.md +1 -1
- package/skills/fo/fo-review/SKILL.md +1 -1
- package/skills/fo/fo-session-retro/SKILL.md +8 -0
- package/skills/fo/fo-session-save/SKILL.md +8 -0
- package/src/cli-output.ts +59 -0
- package/src/config/__tests__/resolve-terminology.test.ts +45 -0
- package/src/config/forge-config.ts +524 -0
- package/src/forge-module.ts +39 -0
- package/src/index.ts +172 -0
- package/src/knowledge/__tests__/promote.test.ts +219 -0
- package/src/knowledge/__tests__/serialize.test.ts +192 -0
- package/src/knowledge/budgets.ts +202 -0
- package/src/knowledge/compact.ts +405 -0
- package/src/knowledge/index.ts +53 -0
- package/src/knowledge/parse.ts +227 -0
- package/src/knowledge/promote.ts +156 -0
- package/src/knowledge/schema.ts +112 -0
- package/src/knowledge/serialize.ts +72 -0
- package/src/migration-adapters/__tests__/ignored-files.test.ts +51 -0
- package/src/migration-adapters/__tests__/registry.test.ts +131 -0
- package/src/migration-adapters/__tests__/types.test.ts +86 -0
- package/src/migration-adapters/git-utils.ts +71 -0
- package/src/migration-adapters/ignored-files.ts +221 -0
- package/src/migration-adapters/index.ts +21 -0
- package/src/migration-adapters/node-typescript-pnpm/index.ts +189 -0
- package/src/migration-adapters/phaser-pnpm/index.ts +189 -0
- package/src/migration-adapters/registry.ts +61 -0
- package/src/migration-adapters/types.ts +68 -0
- package/src/onboarding/__tests__/workspace-discovery.test.ts +134 -0
- package/src/onboarding/agents-generate.ts +443 -0
- package/src/onboarding/create.ts +427 -0
- package/src/onboarding/doctor.ts +1297 -0
- package/src/onboarding/init.ts +337 -0
- package/src/onboarding/invariant-engine.ts +448 -0
- package/src/onboarding/memory-scaffold.ts +189 -0
- package/src/onboarding/nested-agents-generate.ts +93 -0
- package/src/onboarding/nested-agents-templates.ts +233 -0
- package/src/onboarding/profile-validate.ts +139 -0
- package/src/onboarding/scaffold-project.ts +299 -0
- package/src/onboarding/scaffold.ts +143 -0
- package/src/onboarding/upgrade.ts +558 -0
- package/src/onboarding/workspace-discovery.ts +167 -0
- package/src/profiles/__tests__/profile-schema.test.ts +228 -0
- package/src/profiles/__tests__/stack-profile.test.ts +190 -0
- package/src/profiles/__tests__/terminology-utils.test.ts +71 -0
- package/src/profiles/profile-schema.ts +350 -0
- package/src/profiles/stack-profile.ts +188 -0
- package/src/profiles/terminology-utils.ts +36 -0
- package/src/registry.ts +185 -0
- package/src/skill-schema.ts +37 -0
- package/src/tests/acceptance-criteria.test.ts +99 -0
- package/src/tests/adr-implement-stamp.test.ts +368 -0
- package/src/tests/agents-generate-domain.test.ts +317 -0
- package/src/tests/agents-generate.test.ts +458 -0
- package/src/tests/bindings-schema.test.ts +314 -0
- package/src/tests/budgets.test.ts +213 -0
- package/src/tests/cli-output.test.ts +196 -0
- package/src/tests/compact.test.ts +796 -0
- package/src/tests/create.test.ts +289 -0
- package/src/tests/doctor-autonomy.test.ts +98 -0
- package/src/tests/doctor-bindings.test.ts +286 -0
- package/src/tests/doctor-domain.test.ts +113 -0
- package/src/tests/exploration-handlers.test.ts +256 -0
- package/src/tests/fixtures/agents-generate-business-before.txt +288 -0
- package/src/tests/forge-config.test.ts +420 -0
- package/src/tests/fs-atomic.test.ts +96 -0
- package/src/tests/fs.test.ts +103 -0
- package/src/tests/generated-marker.test.ts +161 -0
- package/src/tests/hash.test.ts +37 -0
- package/src/tests/implement-stamp.test.ts +505 -0
- package/src/tests/init-bindings.test.ts +183 -0
- package/src/tests/knowledge-parse.test.ts +307 -0
- package/src/tests/knowledge-pbt.test.ts +164 -0
- package/src/tests/memory-scaffold.test.ts +192 -0
- package/src/tests/migration-adapters.test.ts +480 -0
- package/src/tests/package-files.test.ts +54 -0
- package/src/tests/pinned-check.test.ts +194 -0
- package/src/tests/pinned-init.test.ts +126 -0
- package/src/tests/profile-schema.test.ts +194 -0
- package/src/tests/profile-validate.test.ts +120 -0
- package/src/tests/program-lease.test.ts +245 -0
- package/src/tests/program-paths-extended.test.ts +117 -0
- package/src/tests/program-paths.test.ts +98 -0
- package/src/tests/program-property.test.ts +139 -0
- package/src/tests/program-schemas.test.ts +445 -0
- package/src/tests/program-spec-node.test.ts +250 -0
- package/src/tests/promote.test.ts +481 -0
- package/src/tests/registry.test.ts +73 -0
- package/src/tests/scaffold-project.test.ts +150 -0
- package/src/tests/session-handlers.test.ts +503 -0
- package/src/tests/session-pbt.test.ts +190 -0
- package/src/tests/skill-schema.test.ts +146 -0
- package/src/tests/skill-validate-knowledge.test.ts +26 -0
- package/src/tests/skill-validate.test.ts +254 -0
- package/src/tests/stack-profile.test.ts +221 -0
- package/src/tests/string-utils.test.ts +42 -0
- package/src/tests/upgrade.test.ts +428 -0
- package/src/tests/werkstatt-lock.test.ts +343 -0
- package/src/tests/workspace-discovery-domain.test.ts +112 -0
- package/src/tests/workspace-discovery.test.ts +135 -0
- package/src/types.ts +221 -0
- package/src/utils/__tests__/fs-idempotent.test.ts +73 -0
- package/src/utils/__tests__/fs-trash.test.ts +45 -0
- package/src/utils/fs-atomic.ts +93 -0
- package/src/utils/fs-idempotent.ts +41 -0
- package/src/utils/fs-trash-sync.ts +37 -0
- package/src/utils/fs-trash.ts +24 -0
- package/src/utils/fs.ts +74 -0
- package/src/utils/generated-marker.ts +166 -0
- package/src/utils/hash.ts +19 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/string-utils.ts +19 -0
- package/src/validators/__tests__/note-orphan-detect.test.ts +94 -0
- package/src/validators/note-frontmatter-validate.ts +142 -0
- package/src/validators/note-link-validate.ts +145 -0
- package/src/validators/note-orphan-detect.ts +146 -0
- package/src/validators/port-validate.ts +97 -0
- package/src/validators/skill-validate.ts +831 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Register the program packet control plane commands with the forge
|
|
4
|
+
kernel registry (RFC-0856).</purpose>
|
|
5
|
+
<non-goals>
|
|
6
|
+
<item>Do not implement command logic here — handlers live in handlers/*.ts.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0856: initial program module registration.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ForgeModule } from "../../src/forge-module.ts";
|
|
15
|
+
|
|
16
|
+
export const forgeProgramModule: ForgeModule = {
|
|
17
|
+
name: "forge-program",
|
|
18
|
+
version: "0.1.0",
|
|
19
|
+
|
|
20
|
+
async register(registry) {
|
|
21
|
+
const { runValidate } = await import("./handlers/validate.ts");
|
|
22
|
+
const { runSeal } = await import("./handlers/seal.ts");
|
|
23
|
+
const { runLease } = await import("./handlers/lease.ts");
|
|
24
|
+
const { runComplete } = await import("./handlers/complete.ts");
|
|
25
|
+
|
|
26
|
+
registry.registerCommand({
|
|
27
|
+
name: "program.packet.validate",
|
|
28
|
+
description:
|
|
29
|
+
"Validate a program packet against schema, source hashes, branch/head, " +
|
|
30
|
+
"and state machine rules. Read-only. " +
|
|
31
|
+
"Usage: program.packet.validate --program=RFC-XXXX --packet=NNN-foo --phase=draft --json",
|
|
32
|
+
scope: "workspace",
|
|
33
|
+
supportsAllSites: false,
|
|
34
|
+
flags: {
|
|
35
|
+
program: {
|
|
36
|
+
kind: "string",
|
|
37
|
+
required: true,
|
|
38
|
+
description: "Program RFC id (e.g. RFC-0855).",
|
|
39
|
+
},
|
|
40
|
+
packet: {
|
|
41
|
+
kind: "string",
|
|
42
|
+
required: true,
|
|
43
|
+
description: "Packet id (e.g. 010-node-24).",
|
|
44
|
+
},
|
|
45
|
+
phase: {
|
|
46
|
+
kind: "string",
|
|
47
|
+
description: "Validation phase: draft, sealed, active, or completion.",
|
|
48
|
+
},
|
|
49
|
+
json: {
|
|
50
|
+
kind: "boolean",
|
|
51
|
+
description: "Machine-readable JSON output.",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
reads: ["docs/plans/**/program.yaml", "docs/plans/**/*.md"],
|
|
55
|
+
cacheable: false,
|
|
56
|
+
execute: runValidate,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
registry.registerCommand({
|
|
60
|
+
name: "program.packet.seal",
|
|
61
|
+
description:
|
|
62
|
+
"Steward finalizes a packet against the predecessor's completion commit, " +
|
|
63
|
+
"updates packet state from draft to sealed, and records the seal in the " +
|
|
64
|
+
"program manifest. Does not commit — use ecosystem.commit after. " +
|
|
65
|
+
"Usage: program.packet.seal --program=RFC-XXXX --packet=NNN-foo " +
|
|
66
|
+
"--steward=human:id --idempotency-key=<key> --json",
|
|
67
|
+
scope: "workspace",
|
|
68
|
+
supportsAllSites: false,
|
|
69
|
+
mutatesState: true,
|
|
70
|
+
flags: {
|
|
71
|
+
program: {
|
|
72
|
+
kind: "string",
|
|
73
|
+
required: true,
|
|
74
|
+
description: "Program RFC id.",
|
|
75
|
+
},
|
|
76
|
+
packet: {
|
|
77
|
+
kind: "string",
|
|
78
|
+
required: true,
|
|
79
|
+
description: "Packet id to seal.",
|
|
80
|
+
},
|
|
81
|
+
steward: {
|
|
82
|
+
kind: "string",
|
|
83
|
+
required: true,
|
|
84
|
+
description: "Steward actor id (human:<id> or agent:<id>).",
|
|
85
|
+
},
|
|
86
|
+
"idempotency-key": {
|
|
87
|
+
kind: "string",
|
|
88
|
+
description: "Idempotency key for retry-safe sealing.",
|
|
89
|
+
},
|
|
90
|
+
json: {
|
|
91
|
+
kind: "boolean",
|
|
92
|
+
description: "Machine-readable JSON output.",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
writes: ["docs/plans/**/program.yaml", "docs/plans/**/*.md"],
|
|
96
|
+
reads: ["docs/plans/**/program.yaml", "docs/plans/**/*.md"],
|
|
97
|
+
cacheable: false,
|
|
98
|
+
execute: runSeal,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
registry.registerCommand({
|
|
102
|
+
name: "program.packet.lease",
|
|
103
|
+
description:
|
|
104
|
+
"Manage the exclusive local executor lease for a sealed packet. " +
|
|
105
|
+
"Actions: start, heartbeat, release, recover. " +
|
|
106
|
+
"Raw lease tokens are never persisted — only the SHA-256 hash. " +
|
|
107
|
+
"Usage: program.packet.lease --program=RFC-XXXX --packet=NNN-foo " +
|
|
108
|
+
"--action=start --executor=agent:id --json",
|
|
109
|
+
scope: "workspace",
|
|
110
|
+
supportsAllSites: false,
|
|
111
|
+
flags: {
|
|
112
|
+
program: {
|
|
113
|
+
kind: "string",
|
|
114
|
+
required: true,
|
|
115
|
+
description: "Program RFC id.",
|
|
116
|
+
},
|
|
117
|
+
packet: {
|
|
118
|
+
kind: "string",
|
|
119
|
+
required: true,
|
|
120
|
+
description: "Packet id.",
|
|
121
|
+
},
|
|
122
|
+
action: {
|
|
123
|
+
kind: "string",
|
|
124
|
+
required: true,
|
|
125
|
+
description: "Lease action: start, heartbeat, release, or recover.",
|
|
126
|
+
},
|
|
127
|
+
executor: {
|
|
128
|
+
kind: "string",
|
|
129
|
+
description: "Executor actor id (required for --action=start).",
|
|
130
|
+
},
|
|
131
|
+
steward: {
|
|
132
|
+
kind: "string",
|
|
133
|
+
description: "Steward actor id (required for --action=recover).",
|
|
134
|
+
},
|
|
135
|
+
"lease-token": {
|
|
136
|
+
kind: "string",
|
|
137
|
+
description: "Opaque lease token (required for heartbeat and release).",
|
|
138
|
+
},
|
|
139
|
+
reason: {
|
|
140
|
+
kind: "string",
|
|
141
|
+
description: "Recovery reason (required for --action=recover).",
|
|
142
|
+
},
|
|
143
|
+
json: {
|
|
144
|
+
kind: "boolean",
|
|
145
|
+
description: "Machine-readable JSON output.",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
writes: [".forge/program-leases/**"],
|
|
149
|
+
reads: ["docs/plans/**/program.yaml", "docs/plans/**/*.md"],
|
|
150
|
+
cacheable: false,
|
|
151
|
+
execute: runLease,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
registry.registerCommand({
|
|
155
|
+
name: "program.packet.complete",
|
|
156
|
+
description:
|
|
157
|
+
"Steward validates the implementation range, writes the completion report, " +
|
|
158
|
+
"and updates the program manifest. Supports --bootstrap for packet 000. " +
|
|
159
|
+
"Does not commit — use ecosystem.commit after. " +
|
|
160
|
+
"Usage: program.packet.complete --program=RFC-XXXX --packet=NNN-foo " +
|
|
161
|
+
"--steward=human:id --lease-token=<token> --implementation-head=<sha> --json",
|
|
162
|
+
scope: "workspace",
|
|
163
|
+
supportsAllSites: false,
|
|
164
|
+
mutatesState: true,
|
|
165
|
+
flags: {
|
|
166
|
+
program: {
|
|
167
|
+
kind: "string",
|
|
168
|
+
required: true,
|
|
169
|
+
description: "Program RFC id.",
|
|
170
|
+
},
|
|
171
|
+
packet: {
|
|
172
|
+
kind: "string",
|
|
173
|
+
required: true,
|
|
174
|
+
description: "Packet id to complete.",
|
|
175
|
+
},
|
|
176
|
+
steward: {
|
|
177
|
+
kind: "string",
|
|
178
|
+
required: true,
|
|
179
|
+
description: "Steward actor id (must differ from executor).",
|
|
180
|
+
},
|
|
181
|
+
"lease-token": {
|
|
182
|
+
kind: "string",
|
|
183
|
+
description: "Lease token (required for non-bootstrap).",
|
|
184
|
+
},
|
|
185
|
+
"implementation-head": {
|
|
186
|
+
kind: "string",
|
|
187
|
+
description: "SHA of the implementation head commit (required for non-bootstrap).",
|
|
188
|
+
},
|
|
189
|
+
"idempotency-key": {
|
|
190
|
+
kind: "string",
|
|
191
|
+
description: "Idempotency key for retry-safe completion.",
|
|
192
|
+
},
|
|
193
|
+
bootstrap: {
|
|
194
|
+
kind: "boolean",
|
|
195
|
+
description: "Bootstrap completion (packet 000 only).",
|
|
196
|
+
},
|
|
197
|
+
"seal-commit": {
|
|
198
|
+
kind: "string",
|
|
199
|
+
description: "Seal commit SHA (required for --bootstrap).",
|
|
200
|
+
},
|
|
201
|
+
json: {
|
|
202
|
+
kind: "boolean",
|
|
203
|
+
description: "Machine-readable JSON output.",
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
writes: ["docs/plans/**/completions/**", "docs/plans/**/program.yaml"],
|
|
207
|
+
reads: ["docs/plans/**/program.yaml", "docs/plans/**/*.md"],
|
|
208
|
+
cacheable: false,
|
|
209
|
+
execute: runComplete,
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Zod schemas for the forge/program@1 control plane — program manifest,
|
|
4
|
+
packet, lease, completion, and recovery records. All schemas are strict and
|
|
5
|
+
reject unknown fields (RFC-0856).</purpose>
|
|
6
|
+
<non-goals>
|
|
7
|
+
<item>Do not import from @warpgogol/* — forge must remain dependency-free.</item>
|
|
8
|
+
<item>Do not add command logic here — schemas only.</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
<CHANGE_SUMMARY>
|
|
12
|
+
<item>RFC-0856: initial program control-plane schemas.</item>
|
|
13
|
+
<item>RFC-0857: phase-aware lease schema (preparation | execution) and preparation report schema.</item>
|
|
14
|
+
</CHANGE_SUMMARY>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { z } from "zod/v4";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Shared primitives
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
export const programActorSchema = z
|
|
24
|
+
.string()
|
|
25
|
+
.regex(/^(human|agent):.+$/, "Must be 'human:<id>' or 'agent:<id>'");
|
|
26
|
+
|
|
27
|
+
export const programStateSchema = z.enum(["preparing", "executing", "blocked", "complete"]);
|
|
28
|
+
|
|
29
|
+
export const packetStateSchema = z.enum(["draft", "sealed", "active", "completed", "blocked"]);
|
|
30
|
+
|
|
31
|
+
export const recoveryStatusSchema = z.enum(["verified", "not-applicable"]);
|
|
32
|
+
|
|
33
|
+
export const programLeasePhaseSchema = z.enum(["preparation", "execution"]);
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Program manifest: forge/program@1
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
export const programPacketIndexEntrySchema = z.object({
|
|
40
|
+
order: z.number().int().min(0),
|
|
41
|
+
packetId: z.string().min(1),
|
|
42
|
+
file: z.string().min(1),
|
|
43
|
+
state: packetStateSchema,
|
|
44
|
+
governingDecision: z.string().min(1),
|
|
45
|
+
decisionKind: z.enum(["rfc", "spec-amendment", "spec-node"]),
|
|
46
|
+
resolvedRfc: z.string().nullable(),
|
|
47
|
+
dependsOnPacket: z.string().nullable(),
|
|
48
|
+
baseCommit: z.string().nullable(),
|
|
49
|
+
sealCommit: z.string().nullable(),
|
|
50
|
+
completion: z.string().nullable(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const programManifestSchema = z
|
|
54
|
+
.object({
|
|
55
|
+
schema: z.literal("forge/program@1"),
|
|
56
|
+
program: z.string().min(1),
|
|
57
|
+
title: z.string().min(1),
|
|
58
|
+
branch: z.string().min(1),
|
|
59
|
+
state: programStateSchema,
|
|
60
|
+
currentPacket: z.string().min(1),
|
|
61
|
+
steward: programActorSchema,
|
|
62
|
+
sequential: z.literal(true),
|
|
63
|
+
parallelism: z.literal(1),
|
|
64
|
+
packets: z.array(programPacketIndexEntrySchema).min(1),
|
|
65
|
+
})
|
|
66
|
+
.strict();
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Program packet: forge/program-packet@1
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
export const normativeSourceSchema = z.object({
|
|
73
|
+
path: z.string().min(1),
|
|
74
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/, "Must be a SHA-256 hex digest"),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const requiredValidationSchema = z.object({
|
|
78
|
+
command: z.string().min(1),
|
|
79
|
+
expectedStatus: z.literal("pass"),
|
|
80
|
+
expectedDiagnostics: z.array(z.string()),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const programPacketSchema = z
|
|
84
|
+
.object({
|
|
85
|
+
schema: z.literal("forge/program-packet@1"),
|
|
86
|
+
program: z.string().min(1),
|
|
87
|
+
packetId: z.string().min(1),
|
|
88
|
+
state: z.enum(["draft", "sealed"]),
|
|
89
|
+
governingDecision: z.string().min(1),
|
|
90
|
+
decisionKind: z.enum(["rfc", "spec-amendment", "spec-node"]),
|
|
91
|
+
resolvedRfc: z.string().nullable(),
|
|
92
|
+
dependsOnPacket: z.string().nullable(),
|
|
93
|
+
baseCommit: z.string().nullable(),
|
|
94
|
+
branch: z.string().min(1),
|
|
95
|
+
steward: programActorSchema,
|
|
96
|
+
normativeSources: z.array(normativeSourceSchema),
|
|
97
|
+
allowedFiles: z.array(z.string().min(1)).min(1),
|
|
98
|
+
forbiddenFiles: z.array(z.string().min(1)),
|
|
99
|
+
permittedTransitionDiagnostics: z.array(z.string()),
|
|
100
|
+
requiredValidations: z.array(requiredValidationSchema),
|
|
101
|
+
})
|
|
102
|
+
.strict();
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Program packet lease: forge/program-packet-lease@1
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
export const programPacketLeaseSchema = z
|
|
109
|
+
.object({
|
|
110
|
+
schema: z.literal("forge/program-packet-lease@1"),
|
|
111
|
+
program: z.string().min(1),
|
|
112
|
+
packetId: z.string().min(1),
|
|
113
|
+
phase: programLeasePhaseSchema,
|
|
114
|
+
actor: programActorSchema,
|
|
115
|
+
baseCommit: z.string().min(1),
|
|
116
|
+
sealCommit: z.string().nullable(),
|
|
117
|
+
tokenHash: z.string().regex(/^[0-9a-f]{64}$/, "Must be a SHA-256 hex digest"),
|
|
118
|
+
startedAt: z.string().min(1),
|
|
119
|
+
heartbeatAt: z.string().min(1),
|
|
120
|
+
timeoutSeconds: z.number().int().positive(),
|
|
121
|
+
})
|
|
122
|
+
.strict();
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Program packet preparation: forge/program-packet-preparation@1 (RFC-0857)
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
export const preparationValidationSchema = z.object({
|
|
129
|
+
command: z.string().min(1),
|
|
130
|
+
status: z.literal("pass"),
|
|
131
|
+
evidenceDigest: z.string().min(1),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export const programPacketPreparationSchema = z
|
|
135
|
+
.object({
|
|
136
|
+
schema: z.literal("forge/program-packet-preparation@1"),
|
|
137
|
+
program: z.string().min(1),
|
|
138
|
+
packetId: z.string().min(1),
|
|
139
|
+
baseCommit: z.string().min(1),
|
|
140
|
+
preparationCommits: z.array(z.string().min(1)),
|
|
141
|
+
preparationHead: z.string().min(1),
|
|
142
|
+
changedFiles: z.array(z.string().min(1)),
|
|
143
|
+
governingDecision: z.string().min(1),
|
|
144
|
+
resolvedRfc: z.string().min(1),
|
|
145
|
+
materializationCommit: z.string().min(1),
|
|
146
|
+
validations: z.array(preparationValidationSchema),
|
|
147
|
+
cleanTrees: z.literal(true),
|
|
148
|
+
preparedBy: programActorSchema,
|
|
149
|
+
})
|
|
150
|
+
.strict();
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Program packet completion: forge/program-packet-completion@1
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
export const completionValidationSchema = z.object({
|
|
157
|
+
command: z.string().min(1),
|
|
158
|
+
status: z.literal("pass"),
|
|
159
|
+
evidenceDigest: z.string().min(1),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
export const programPacketCompletionSchema = z
|
|
163
|
+
.object({
|
|
164
|
+
schema: z.literal("forge/program-packet-completion@1"),
|
|
165
|
+
program: z.string().min(1),
|
|
166
|
+
packetId: z.string().min(1),
|
|
167
|
+
baseCommit: z.string().min(1),
|
|
168
|
+
sealCommit: z.string().min(1),
|
|
169
|
+
implementationCommits: z.array(z.string().min(1)),
|
|
170
|
+
implementationHead: z.string().min(1),
|
|
171
|
+
changedFiles: z.array(z.string().min(1)),
|
|
172
|
+
validations: z.array(completionValidationSchema),
|
|
173
|
+
remainingTransitionDiagnostics: z.array(z.string()),
|
|
174
|
+
unexpectedDiagnostics: z.array(z.string()).length(0),
|
|
175
|
+
recoveryStatus: recoveryStatusSchema,
|
|
176
|
+
cleanTrees: z.literal(true),
|
|
177
|
+
completedBy: programActorSchema,
|
|
178
|
+
})
|
|
179
|
+
.strict();
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Recovery record: forge/program-packet-recovery@1
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
export const recoveryRecordSchema = z
|
|
186
|
+
.object({
|
|
187
|
+
schema: z.literal("forge/program-packet-recovery@1"),
|
|
188
|
+
program: z.string().min(1),
|
|
189
|
+
packetId: z.string().min(1),
|
|
190
|
+
previousLeaseDigest: z.string().min(1),
|
|
191
|
+
reason: z.string().min(1),
|
|
192
|
+
actor: programActorSchema,
|
|
193
|
+
observedHead: z.string().min(1),
|
|
194
|
+
target: z.enum(["blocked", "sealed"]),
|
|
195
|
+
recoveredAt: z.string().min(1),
|
|
196
|
+
})
|
|
197
|
+
.strict();
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// TypeScript types (inferred from schemas)
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
export type ProgramActor = z.infer<typeof programActorSchema>;
|
|
204
|
+
export type ProgramState = z.infer<typeof programStateSchema>;
|
|
205
|
+
export type PacketState = z.infer<typeof packetStateSchema>;
|
|
206
|
+
export type RecoveryStatus = z.infer<typeof recoveryStatusSchema>;
|
|
207
|
+
export type ProgramManifest = z.infer<typeof programManifestSchema>;
|
|
208
|
+
export type ProgramPacketIndexEntry = z.infer<typeof programPacketIndexEntrySchema>;
|
|
209
|
+
export type ProgramPacket = z.infer<typeof programPacketSchema>;
|
|
210
|
+
export type NormativeSource = z.infer<typeof normativeSourceSchema>;
|
|
211
|
+
export type RequiredValidation = z.infer<typeof requiredValidationSchema>;
|
|
212
|
+
export type ProgramLeasePhase = z.infer<typeof programLeasePhaseSchema>;
|
|
213
|
+
export type ProgramPacketLease = z.infer<typeof programPacketLeaseSchema>;
|
|
214
|
+
export type PreparationValidation = z.infer<typeof preparationValidationSchema>;
|
|
215
|
+
export type ProgramPacketPreparation = z.infer<typeof programPacketPreparationSchema>;
|
|
216
|
+
export type ProgramPacketCompletion = z.infer<typeof programPacketCompletionSchema>;
|
|
217
|
+
export type CompletionValidation = z.infer<typeof completionValidationSchema>;
|
|
218
|
+
export type RecoveryRecord = z.infer<typeof recoveryRecordSchema>;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>State machine logic for program packet transitions.
|
|
4
|
+
Validates allowed transitions and produces violation records.</purpose>
|
|
5
|
+
<non-goals>
|
|
6
|
+
<item>Do not mutate files — state validation only.</item>
|
|
7
|
+
<item>Do not import from @warpgogol/* — forge must remain dependency-free.</item>
|
|
8
|
+
</non-goals>
|
|
9
|
+
</MODULE_CONTRACT>
|
|
10
|
+
<CHANGE_SUMMARY>
|
|
11
|
+
<item>RFC-0856: initial state machine logic.</item>
|
|
12
|
+
<item>RFC-0857: spec-node resolvedRfc validation at seal boundary (PROGRAM-PACKET-12).</item>
|
|
13
|
+
</CHANGE_SUMMARY>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type {
|
|
17
|
+
ProgramManifest,
|
|
18
|
+
ProgramPacket,
|
|
19
|
+
ProgramPacketIndexEntry,
|
|
20
|
+
ProgramPacketLease,
|
|
21
|
+
ProgramActor,
|
|
22
|
+
} from "./schemas.ts";
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Violation type
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
export interface PacketViolation {
|
|
29
|
+
rule: string;
|
|
30
|
+
path?: string;
|
|
31
|
+
message: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Transition validation
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate that a seal transition is allowed.
|
|
40
|
+
*/
|
|
41
|
+
export function validateSealTransition(
|
|
42
|
+
manifest: ProgramManifest,
|
|
43
|
+
entry: ProgramPacketIndexEntry,
|
|
44
|
+
packet: ProgramPacket,
|
|
45
|
+
options: {
|
|
46
|
+
branch: string;
|
|
47
|
+
head: string;
|
|
48
|
+
isClean: boolean;
|
|
49
|
+
hasActiveLease: boolean;
|
|
50
|
+
},
|
|
51
|
+
): PacketViolation[] {
|
|
52
|
+
const violations: PacketViolation[] = [];
|
|
53
|
+
|
|
54
|
+
// PROGRAM-PACKET-02: wrong branch
|
|
55
|
+
if (options.branch !== manifest.branch) {
|
|
56
|
+
violations.push({
|
|
57
|
+
rule: "PROGRAM-PACKET-02",
|
|
58
|
+
message: `wrong branch: expected ${manifest.branch}, got ${options.branch}`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// PROGRAM-PACKET-10: dirty tree at sealed boundary
|
|
63
|
+
if (!options.isClean) {
|
|
64
|
+
violations.push({
|
|
65
|
+
rule: "PROGRAM-PACKET-10",
|
|
66
|
+
message: "dirty tree at seal boundary — commit or stash changes first",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// PROGRAM-PACKET-05: active or stale lease exists
|
|
71
|
+
if (options.hasActiveLease) {
|
|
72
|
+
violations.push({
|
|
73
|
+
rule: "PROGRAM-PACKET-05",
|
|
74
|
+
message: "active or stale lease exists — recover before sealing",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Packet must be in draft state
|
|
79
|
+
if (packet.state !== "draft") {
|
|
80
|
+
violations.push({
|
|
81
|
+
rule: "PROGRAM-PACKET-04",
|
|
82
|
+
message: `packet state is ${packet.state}, expected draft`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// PROGRAM-PACKET-03: predecessor must be completed (unless bootstrap)
|
|
87
|
+
if (entry.dependsOnPacket !== null) {
|
|
88
|
+
const predecessor = manifest.packets.find((p) => p.packetId === entry.dependsOnPacket);
|
|
89
|
+
if (!predecessor) {
|
|
90
|
+
violations.push({
|
|
91
|
+
rule: "PROGRAM-PACKET-03",
|
|
92
|
+
message: `predecessor packet ${entry.dependsOnPacket} not found in manifest`,
|
|
93
|
+
});
|
|
94
|
+
} else if (predecessor.state !== "completed") {
|
|
95
|
+
violations.push({
|
|
96
|
+
rule: "PROGRAM-PACKET-03",
|
|
97
|
+
message: `predecessor packet ${entry.dependsOnPacket} is ${predecessor.state}, expected completed`,
|
|
98
|
+
});
|
|
99
|
+
} else if (predecessor.completion && entry.baseCommit !== predecessor.completion) {
|
|
100
|
+
violations.push({
|
|
101
|
+
rule: "PROGRAM-PACKET-09",
|
|
102
|
+
message: `baseCommit ${entry.baseCommit} does not match predecessor completion ${predecessor.completion}`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// baseCommit must be set for sealed packet (unless bootstrap with null predecessor)
|
|
108
|
+
if (entry.dependsOnPacket !== null && entry.baseCommit === null) {
|
|
109
|
+
violations.push({
|
|
110
|
+
rule: "PROGRAM-PACKET-09",
|
|
111
|
+
message: "baseCommit is null for a non-bootstrap packet",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// allowedFiles must be non-empty
|
|
116
|
+
if (packet.allowedFiles.length === 0) {
|
|
117
|
+
violations.push({
|
|
118
|
+
rule: "PROGRAM-PACKET-06",
|
|
119
|
+
message: "allowedFiles is empty — packet must define at least one allowed path",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// RFC-0857: spec-node packets require a resolved RFC that is accepted or implemented
|
|
124
|
+
if (entry.decisionKind === "spec-node") {
|
|
125
|
+
if (entry.resolvedRfc === null) {
|
|
126
|
+
violations.push({
|
|
127
|
+
rule: "PROGRAM-PACKET-12",
|
|
128
|
+
message: `spec-node packet ${entry.packetId} has resolvedRfc: null — materialize and accept the governing RFC before sealing`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return violations;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Validate that a lease start transition is allowed.
|
|
138
|
+
*/
|
|
139
|
+
export function validateLeaseStartTransition(
|
|
140
|
+
manifest: ProgramManifest,
|
|
141
|
+
entry: ProgramPacketIndexEntry,
|
|
142
|
+
packet: ProgramPacket,
|
|
143
|
+
options: {
|
|
144
|
+
head: string;
|
|
145
|
+
steward: ProgramActor;
|
|
146
|
+
actor: ProgramActor;
|
|
147
|
+
hasActiveLease: boolean;
|
|
148
|
+
},
|
|
149
|
+
): PacketViolation[] {
|
|
150
|
+
const violations: PacketViolation[] = [];
|
|
151
|
+
|
|
152
|
+
// PROGRAM-PACKET-04: role collision
|
|
153
|
+
if (options.steward === options.actor) {
|
|
154
|
+
violations.push({
|
|
155
|
+
rule: "PROGRAM-PACKET-04",
|
|
156
|
+
message: `role collision: steward and actor are the same (${options.steward})`,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Packet must be sealed
|
|
161
|
+
if (packet.state !== "sealed") {
|
|
162
|
+
violations.push({
|
|
163
|
+
rule: "PROGRAM-PACKET-04",
|
|
164
|
+
message: `packet state is ${packet.state}, expected sealed`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// PROGRAM-PACKET-05: active or stale lease exists
|
|
169
|
+
if (options.hasActiveLease) {
|
|
170
|
+
violations.push({
|
|
171
|
+
rule: "PROGRAM-PACKET-05",
|
|
172
|
+
message: "active or stale lease already exists for this packet",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// PROGRAM-PACKET-02: HEAD must match sealCommit
|
|
177
|
+
if (entry.sealCommit && options.head !== entry.sealCommit) {
|
|
178
|
+
violations.push({
|
|
179
|
+
rule: "PROGRAM-PACKET-02",
|
|
180
|
+
message: `HEAD ${options.head} does not match sealCommit ${entry.sealCommit}`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return violations;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validate that a completion transition is allowed.
|
|
189
|
+
*/
|
|
190
|
+
export function validateCompleteTransition(
|
|
191
|
+
manifest: ProgramManifest,
|
|
192
|
+
entry: ProgramPacketIndexEntry,
|
|
193
|
+
lease: ProgramPacketLease,
|
|
194
|
+
options: {
|
|
195
|
+
head: string;
|
|
196
|
+
isClean: boolean;
|
|
197
|
+
steward: ProgramActor;
|
|
198
|
+
implementationHead: string;
|
|
199
|
+
changedFiles: string[];
|
|
200
|
+
allowedFiles: string[];
|
|
201
|
+
forbiddenFiles: string[];
|
|
202
|
+
ancestryValid: boolean;
|
|
203
|
+
},
|
|
204
|
+
): PacketViolation[] {
|
|
205
|
+
const violations: PacketViolation[] = [];
|
|
206
|
+
|
|
207
|
+
// PROGRAM-PACKET-04: steward must differ from actor
|
|
208
|
+
if (options.steward === lease.actor) {
|
|
209
|
+
violations.push({
|
|
210
|
+
rule: "PROGRAM-PACKET-04",
|
|
211
|
+
message: "completion steward must differ from lease actor",
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// PROGRAM-PACKET-10: dirty tree
|
|
216
|
+
if (!options.isClean) {
|
|
217
|
+
violations.push({
|
|
218
|
+
rule: "PROGRAM-PACKET-10",
|
|
219
|
+
message: "dirty tree at completion boundary",
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// PROGRAM-PACKET-09: ancestry
|
|
224
|
+
if (!options.ancestryValid) {
|
|
225
|
+
violations.push({
|
|
226
|
+
rule: "PROGRAM-PACKET-09",
|
|
227
|
+
message: "implementation commits do not descend from seal commit",
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// PROGRAM-PACKET-09: implementation head must match
|
|
232
|
+
if (options.head !== options.implementationHead) {
|
|
233
|
+
violations.push({
|
|
234
|
+
rule: "PROGRAM-PACKET-09",
|
|
235
|
+
message: `HEAD ${options.head} does not match declared implementationHead ${options.implementationHead}`,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// PROGRAM-PACKET-06: path validation
|
|
240
|
+
for (const file of options.changedFiles) {
|
|
241
|
+
if (options.forbiddenFiles.length > 0 && isPathForbidden(file, options.forbiddenFiles)) {
|
|
242
|
+
violations.push({
|
|
243
|
+
rule: "PROGRAM-PACKET-06",
|
|
244
|
+
path: file,
|
|
245
|
+
message: `changed file ${file} is in forbidden list`,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (!isPathAllowed(file, options.allowedFiles)) {
|
|
249
|
+
violations.push({
|
|
250
|
+
rule: "PROGRAM-PACKET-06",
|
|
251
|
+
path: file,
|
|
252
|
+
message: `changed file ${file} is not in allowed list`,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return violations;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Path helpers (imported from discovery for convenience)
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
import { isPathAllowed, isPathForbidden } from "./discovery.ts";
|