@mgiles/perk 3.2.0 → 3.3.0
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/README.md +5 -0
- package/extension/authoring/gist/draft.ts +198 -0
- package/extension/authoring/gist/prose.ts +46 -0
- package/extension/authoring/gist/review.ts +133 -0
- package/extension/authoring/gist/save.ts +118 -0
- package/extension/authoring/objective/draft.ts +345 -0
- package/extension/{factories/objectiveDreamReport.ts → authoring/objective/dreamReportGate.ts} +74 -131
- package/extension/authoring/objective/planning.ts +124 -0
- package/extension/authoring/objective/prose.ts +103 -0
- package/extension/authoring/objective/review.ts +128 -0
- package/extension/authoring/objective/save.ts +224 -0
- package/extension/authoring/plan/draft.ts +84 -0
- package/extension/authoring/plan/prose.ts +41 -0
- package/extension/authoring/plan/review.ts +269 -0
- package/extension/authoring/plan/save.ts +256 -0
- package/extension/authoring/plan/source.ts +82 -0
- package/extension/authoring/refinement/context.ts +468 -0
- package/extension/authoring/refinement/draft.ts +261 -0
- package/extension/authoring/refinement/prose.ts +79 -0
- package/extension/authoring/refinement/review.ts +111 -0
- package/extension/authoring/refinement/save.ts +119 -0
- package/extension/authoring/review/approvalGate.ts +34 -0
- package/extension/authoring/review/draftContext.ts +68 -0
- package/extension/codeReview/automated.ts +352 -0
- package/extension/codeReview/submission.ts +229 -0
- package/extension/delivery/address.ts +295 -0
- package/extension/delivery/ci.ts +355 -0
- package/extension/delivery/commitCompact.ts +93 -0
- package/extension/delivery/conflictResolution.ts +247 -0
- package/extension/delivery/ready.ts +193 -0
- package/extension/delivery/stackConflict.ts +361 -0
- package/extension/delivery/stackObjective.ts +16 -0
- package/extension/delivery/stackReconcile.ts +165 -0
- package/extension/delivery/submit.ts +171 -0
- package/extension/index.ts +365 -380
- package/extension/learning/analystWave.ts +324 -0
- package/extension/learning/audit.ts +667 -0
- package/extension/learning/capture.ts +92 -0
- package/extension/learning/containment.ts +104 -0
- package/extension/{waves/dreamWave.ts → learning/dream.ts} +112 -94
- package/extension/learning/dreamAnalysis.ts +435 -0
- package/extension/{waves/dreamReducerWave.ts → learning/dreamReducer.ts} +46 -41
- package/extension/{waves → learning}/dreamReport.ts +35 -31
- package/extension/learning/harvest.ts +491 -0
- package/extension/learning/prose.ts +66 -0
- package/extension/learning/routing.ts +79 -0
- package/extension/pi/v1/bashScanTimeout.ts +64 -0
- package/extension/{doors/prReview.ts → pi/v1/codeReview/automated.ts} +215 -311
- package/extension/{doors/prReviewBrowser.ts → pi/v1/codeReview/browser.ts} +53 -33
- package/extension/{doors/hunkHandoff.ts → pi/v1/codeReview/checkout.ts} +12 -8
- package/extension/{doors/reviewWaveTools.ts → pi/v1/codeReview/reviewWave.ts} +146 -114
- package/extension/{doors/stackReviewBrowser.ts → pi/v1/codeReview/stack.ts} +62 -29
- package/extension/pi/v1/codeReview/submit.ts +354 -0
- package/extension/{doors/prReviewTerminal.ts → pi/v1/codeReview/terminal.ts} +32 -27
- package/extension/pi/v1/contextEvidence.ts +80 -0
- package/extension/pi/v1/contextInjection.ts +207 -0
- package/extension/{doors → pi/v1/delivery}/address.ts +154 -267
- package/extension/pi/v1/delivery/ci.ts +570 -0
- package/extension/pi/v1/delivery/commitCompact.ts +201 -0
- package/extension/pi/v1/delivery/conflictResolverEngine.ts +425 -0
- package/extension/{doors → pi/v1/delivery}/land.ts +123 -61
- package/extension/pi/v1/delivery/ready.ts +322 -0
- package/extension/pi/v1/delivery/stackConflictResolver.ts +172 -0
- package/extension/pi/v1/delivery/stackDrive.ts +120 -0
- package/extension/pi/v1/delivery/stackLand.ts +223 -0
- package/extension/pi/v1/delivery/stackRecover.ts +265 -0
- package/extension/pi/v1/delivery/stackStatus.ts +237 -0
- package/extension/pi/v1/delivery/stackSync.ts +658 -0
- package/extension/pi/v1/delivery/submit.ts +389 -0
- package/extension/pi/v1/delivery/submitConflict.ts +186 -0
- package/extension/pi/v1/draftReview.ts +431 -0
- package/extension/{doors → pi/v1}/draftReviewWaveTools.ts +141 -151
- package/extension/pi/v1/gist.ts +794 -0
- package/extension/pi/v1/learning/audit.ts +186 -0
- package/extension/pi/v1/learning/dream.ts +207 -0
- package/extension/{doors/learnFactory.ts → pi/v1/learning/factory.ts} +18 -65
- package/extension/{doors/harvestWaveTools.ts → pi/v1/learning/harvest.ts} +46 -100
- package/extension/pi/v1/learning/learn.ts +585 -0
- package/extension/pi/v1/lifecycleGates.ts +127 -0
- package/extension/{factories → pi/v1}/objective.ts +53 -33
- package/extension/pi/v1/objectiveAuthoring.ts +672 -0
- package/extension/pi/v1/objectiveDreamGate.ts +160 -0
- package/extension/{factories/objectivePlan.ts → pi/v1/objectivePlanning.ts} +328 -533
- package/extension/pi/v1/objectiveRefinement.ts +1320 -0
- package/extension/pi/v1/objectiveReview.ts +451 -0
- package/extension/{doors → pi/v1}/objectiveReviewBrowser.ts +259 -172
- package/extension/pi/v1/plan.ts +812 -0
- package/extension/pi/v1/planReview.ts +820 -0
- package/extension/{doors → pi/v1}/planReviewBrowser.ts +228 -152
- package/extension/{doors/annotationPush.ts → pi/v1/providers/annotations.ts} +158 -89
- package/extension/pi/v1/providers/plannotator.ts +487 -0
- package/extension/{doors → pi/v1/providers}/plannotatorHandoff.ts +73 -27
- package/extension/pi/v1/providers/selection.ts +43 -0
- package/extension/{adapters/planAdapterTombell.ts → pi/v1/providers/tombell.ts} +43 -72
- package/extension/pi/v1/review.ts +538 -0
- package/extension/pi/v1/reviewOutcome.ts +9 -0
- package/extension/pi/v1/scoutWave.ts +318 -0
- package/extension/{doors → pi/v1}/selfcheck.ts +4 -4
- package/extension/session/branchWorkflowSession.ts +60 -0
- package/extension/session/lifecycle.ts +644 -0
- package/extension/session/lifecycleGates.ts +64 -0
- package/extension/session/saveDestination.ts +87 -0
- package/extension/session/workflowSession.ts +971 -0
- package/extension/substrate/agentScratch.ts +27 -54
- package/extension/substrate/bashScanTimeout.ts +181 -0
- package/extension/substrate/bindingDelivery.ts +38 -30
- package/extension/substrate/bindings.ts +4 -5
- package/extension/substrate/cache.ts +64 -12
- package/extension/substrate/childRestrictions.ts +39 -0
- package/extension/substrate/coldDoor.ts +17 -1
- package/extension/substrate/config.ts +157 -21
- package/extension/substrate/git.ts +88 -6
- package/extension/substrate/modelVisible.ts +53 -0
- package/extension/substrate/prompts.ts +22 -0
- package/extension/substrate/registry.ts +2 -0
- package/extension/substrate/resolverLease.ts +5 -4
- package/extension/substrate/sessionData.ts +85 -152
- package/extension/substrate/toolGating.ts +263 -84
- package/extension/substrate/unifiedDiff.ts +1 -1
- package/extension/substrate/workflowState.ts +178 -163
- package/extension/substrate/worktreeResolverLock.ts +261 -0
- package/extension/surfaces/surfaces.ts +79 -27
- package/extension/waves/adversarialReviewWave.ts +87 -46
- package/extension/waves/blockedReports.ts +59 -0
- package/extension/waves/draftReviewWave.ts +42 -42
- package/extension/waves/laneIdentity.ts +77 -0
- package/extension/waves/objectiveExplorerWave.ts +24 -24
- package/extension/waves/prReviewWave.ts +89 -77
- package/extension/waves/reportWave.ts +438 -578
- package/extension/waves/reviewClassifierWave.ts +22 -22
- package/extension/waves/rpcAdapter.ts +100 -15
- package/extension/waves/scoutWave.ts +192 -0
- package/extension/waves/transport.ts +480 -0
- package/extension/worker/sdkAdapter.ts +494 -0
- package/extension/worker/stageExecution.ts +679 -0
- package/extension/workerMain.ts +18 -19
- package/package.json +6 -4
- package/prompts/_fixtures/live.yaml +43 -18
- package/prompts/contexts/adapters/plannotator-gist.md +6 -0
- package/prompts/contexts/adapters/plannotator-objective.md +6 -0
- package/prompts/contexts/adapters/plannotator-plan.md +8 -1
- package/prompts/contexts/adapters/plannotator-refinement.md +22 -0
- package/prompts/contexts/objective-refinement.md +17 -0
- package/prompts/contexts/read-only.md +1 -1
- package/prompts/stages/conflict-resolution-continuation.md +9 -6
- package/prompts/stages/conflict-resolution.md +4 -4
- package/prompts/stages/objective-plan/guidance.md +2 -2
- package/prompts/stages/objective-plan/seed.md +9 -1
- package/prompts/stages/objective-reconcile-ready.md +1 -1
- package/prompts/stages/objective-reconcile.md +1 -1
- package/prompts/stages/objective-refine/seed.md +18 -0
- package/prompts/stages/objective-review-browser.md +4 -4
- package/prompts/stages/objective-sync.md +1 -1
- package/prompts/stages/plan-review-browser.md +4 -4
- package/prompts/stages/pr-review-browser/active.md +3 -4
- package/prompts/stages/pr-review-browser/foreign.md +3 -4
- package/prompts/stages/pr-review-terminal/active.md +3 -3
- package/prompts/stages/pr-review-terminal/foreign.md +3 -3
- package/prompts/stages/pr-review.md +3 -3
- package/prompts/stages/stack-review-browser/stack.md +5 -6
- package/shared/README.md +8 -0
- package/shared/bindings.yaml +3 -3
- package/shared/contracts.md +2601 -506
- package/shared/fixtures/issues-table.json +130 -0
- package/shared/registry.yaml +13 -0
- package/shared/schemas/outputs/objective-node-engagement.schema.json +318 -0
- package/shared/schemas/outputs/objective-stack-status.schema.json +6 -1
- package/shared/schemas/outputs/pr-review-context.schema.json +54 -9
- package/shared/schemas/outputs/pr-review-stack-context.schema.json +196 -0
- package/extension/adapters/planAdapterPlannotator.ts +0 -362
- package/extension/doors/auditWaveTools.ts +0 -352
- package/extension/doors/ciExecutor.ts +0 -756
- package/extension/doors/commitCompact.ts +0 -251
- package/extension/doors/dreamWaveTools.ts +0 -489
- package/extension/doors/learn.ts +0 -668
- package/extension/doors/lifecycleGates.ts +0 -207
- package/extension/doors/objectiveStack.ts +0 -1543
- package/extension/doors/prReviewDynamic.ts +0 -276
- package/extension/doors/ready.ts +0 -279
- package/extension/doors/submit.ts +0 -373
- package/extension/doors/submitPrReview.ts +0 -505
- package/extension/factories/gistAuthor.ts +0 -94
- package/extension/factories/gistDraft.ts +0 -265
- package/extension/factories/gistSave.ts +0 -251
- package/extension/factories/implementHere.ts +0 -116
- package/extension/factories/objectiveAuthor.ts +0 -98
- package/extension/factories/objectiveDraft.ts +0 -466
- package/extension/factories/objectiveSave.ts +0 -366
- package/extension/factories/planDraft.ts +0 -140
- package/extension/factories/planMode.ts +0 -205
- package/extension/factories/planReview.ts +0 -1237
- package/extension/factories/planSave.ts +0 -604
- package/extension/factories/planTitle.ts +0 -141
- package/extension/substrate/structuredOutput.ts +0 -202
- package/extension/waves/auditWave.ts +0 -312
- package/extension/waves/harvestWave.ts +0 -399
- package/extension/waves/learnWave.ts +0 -155
- package/extension/waves/memoryAdapter.ts +0 -139
- package/extension/waves/prReviewDynamicWave.ts +0 -777
- package/extension/worker/readOnlySession.ts +0 -294
- package/extension/worker/worker.ts +0 -899
- package/prompts/stages/pr-review-dynamic.md +0 -7
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// The objective working-draft feature: the fixed artifact constant and the draft operations
|
|
2
|
+
// over the WorkflowSession seam — the objective-flavored sibling of `authoring/plan/draft.ts`,
|
|
3
|
+
// JSON-envelope flavored (deliberately unshared shapes: a generic envelope would share syntax
|
|
4
|
+
// while erasing each flow's recovery semantics).
|
|
5
|
+
//
|
|
6
|
+
// Carve-out doctrine: the ops take NO path/name parameter — the artifact name is the fixed
|
|
7
|
+
// constant `OBJECTIVE_DRAFT_ARTIFACT` and every byte flows through the session seam (file +
|
|
8
|
+
// verified `session_artifacts` pointer), so the only thing they can ever touch is the one
|
|
9
|
+
// working-objective artifact in the current run's data dir (gitignored scratch). Full rewrite
|
|
10
|
+
// per call, never a save — `objective_save`/`/objective-save` still persist to GitHub.
|
|
11
|
+
//
|
|
12
|
+
// Format doctrine: JSON is the storage/transport format, NEVER the human review surface. The
|
|
13
|
+
// artifact carries `{schema_version, title?, prose, roadmap}` (plus, in a perk learn dream
|
|
14
|
+
// session, the gate-written `dream_report` block — contracts §8.63) — the structured roadmap
|
|
15
|
+
// rides verbatim (node-shape validation stays with the Python plane at save time, the
|
|
16
|
+
// `parse_structured_roadmap` path). The review surface reads the draft via
|
|
17
|
+
// `resumeObjectiveDraft` (digest-validated, classified valid/absent/refused) and renders markdown via
|
|
18
|
+
// `renderObjectiveDraft` (the prose + a roadmap table) — never raw JSON; the
|
|
19
|
+
// approval→`objective_save` orchestration feeds the recovered roadmap back as structured JSON.
|
|
20
|
+
//
|
|
21
|
+
// The §8.63 dream gate arrives INJECTED (`resolveDreamGate` — ctx-bound by the adapter) so the
|
|
22
|
+
// module stays session-pure; `dreamReportGate.ts` owns the matrix.
|
|
23
|
+
|
|
24
|
+
import type { SessionArtifactReceipt, WorkflowSession } from "../../session/workflowSession.ts";
|
|
25
|
+
import {
|
|
26
|
+
type DreamReportGateOutcome,
|
|
27
|
+
decodeDreamReportBlock,
|
|
28
|
+
type ObjectiveDreamReportBlock,
|
|
29
|
+
} from "./dreamReportGate.ts";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The reviewed objective delivery vocabulary (contracts §8.45) — the ONE authority every
|
|
33
|
+
* layer derives from: the `DeliveryChoice` union, the tool schema's enum, and the decoder's
|
|
34
|
+
* narrowing all read these values, so a new variant cannot drift between them.
|
|
35
|
+
*/
|
|
36
|
+
export const DELIVERY_CHOICES = ["incremental", "stacked"] as const;
|
|
37
|
+
|
|
38
|
+
/** The reviewed objective delivery choice (contracts §8.45). */
|
|
39
|
+
export type DeliveryChoice = (typeof DELIVERY_CHOICES)[number];
|
|
40
|
+
|
|
41
|
+
/** Whether `value` is a member of the §8.45 delivery vocabulary (the decoder's narrowing). */
|
|
42
|
+
export function isDeliveryChoice(value: string): value is DeliveryChoice {
|
|
43
|
+
return (DELIVERY_CHOICES as readonly string[]).includes(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The fixed working-objective artifact name (one JSON file: prose + the structured roadmap). */
|
|
47
|
+
export const OBJECTIVE_DRAFT_ARTIFACT = "objective-draft.json";
|
|
48
|
+
|
|
49
|
+
/** The typed revise input — decode owns the shape at the tool boundary (pi/v1). */
|
|
50
|
+
export interface ObjectiveDraftInput {
|
|
51
|
+
prose: string;
|
|
52
|
+
title?: string;
|
|
53
|
+
roadmap?: unknown[];
|
|
54
|
+
/** The objective's target branch; omitted to use the repo default. */
|
|
55
|
+
base?: string;
|
|
56
|
+
/** The reviewed delivery choice; omitted ⇒ incremental (the §8.42 absence rule). */
|
|
57
|
+
delivery?: DeliveryChoice;
|
|
58
|
+
/** The dream-report input (perk learn dream only — §8.63); deep validation is the gate's. */
|
|
59
|
+
dream_report?: unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The revise outcome (the `RevisePlanDraftResult` mirror, plus the §8.63 gate arm). `rejected`
|
|
64
|
+
* splits by `reason` AND carries the exact `errorType` the adapter reports (`gate_refused` is
|
|
65
|
+
* the dynamic arm — the resolver's own `invalid_input`/`bad_state`); `unverified` means an
|
|
66
|
+
* effect may have landed but the read-back proof failed. `problem` carries the caller-facing
|
|
67
|
+
* message bytes. `roadmapNodes` rides so the adapter's rendered twin ("… N roadmap nodes")
|
|
68
|
+
* needs no re-parse.
|
|
69
|
+
*/
|
|
70
|
+
export type ReviseObjectiveDraftResult =
|
|
71
|
+
| { status: "revised"; receipt: SessionArtifactReceipt; bytes: number; roadmapNodes: number }
|
|
72
|
+
| { status: "unchanged"; receipt: SessionArtifactReceipt; bytes: number; roadmapNodes: number }
|
|
73
|
+
| {
|
|
74
|
+
status: "rejected";
|
|
75
|
+
reason: "blank_prose" | "no_identity" | "gate_refused" | "write_refused";
|
|
76
|
+
problem: string;
|
|
77
|
+
errorType: "invalid_input" | "no_run_id" | "bad_state" | "write_failed";
|
|
78
|
+
}
|
|
79
|
+
| { status: "unverified"; problem: string };
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Rewrite the working objective draft (a whole-value replacement) through the session seam:
|
|
83
|
+
* serialize the explicit-literal payload (deterministic key order; `title`/`base`/`delivery`
|
|
84
|
+
* omitted when blank/absent; `schema_version` stays 1 — an additive optional field, fail-open
|
|
85
|
+
* readers), gate `dream_report` at write time (§8.63 — validated here so a report-less dream
|
|
86
|
+
* bundle can never reach review), then write. Diagnostic precedence preserved: blank prose →
|
|
87
|
+
* identity → the dream gate → the write. A byte-identical rewrite short-circuits `unchanged`
|
|
88
|
+
* (the session engine owns the probe). Never throws.
|
|
89
|
+
*/
|
|
90
|
+
export function reviseObjectiveDraft(
|
|
91
|
+
input: ObjectiveDraftInput,
|
|
92
|
+
deps: {
|
|
93
|
+
session: WorkflowSession;
|
|
94
|
+
/** The §8.63 gate, adapter-bound (`resolveDreamReportGate` over the production recovery capability). */
|
|
95
|
+
resolveDreamGate: (input: unknown, generatedAt: string) => DreamReportGateOutcome;
|
|
96
|
+
},
|
|
97
|
+
): ReviseObjectiveDraftResult {
|
|
98
|
+
if (!input.prose.trim()) {
|
|
99
|
+
return {
|
|
100
|
+
status: "rejected",
|
|
101
|
+
reason: "blank_prose",
|
|
102
|
+
problem: "no objective prose to write (pass the full working draft)",
|
|
103
|
+
errorType: "invalid_input",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (deps.session.runId === null) {
|
|
107
|
+
return {
|
|
108
|
+
status: "rejected",
|
|
109
|
+
reason: "no_identity",
|
|
110
|
+
problem: "session has no run_id — cannot write the objective-draft artifact",
|
|
111
|
+
errorType: "no_run_id",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// The §8.63 gate: validated at draft-write time via buildDreamReport, the ONE stamp stored
|
|
116
|
+
// with the block; `absent` keeps the payload byte-identical (every non-dream path unchanged).
|
|
117
|
+
const gate = deps.resolveDreamGate(input.dream_report, new Date().toISOString());
|
|
118
|
+
if (gate.kind === "refuse") {
|
|
119
|
+
return {
|
|
120
|
+
status: "rejected",
|
|
121
|
+
reason: "gate_refused",
|
|
122
|
+
problem: gate.detail,
|
|
123
|
+
errorType: gate.errorType,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const title = input.title?.trim();
|
|
128
|
+
const base = input.base?.trim();
|
|
129
|
+
const delivery = input.delivery;
|
|
130
|
+
const roadmap = input.roadmap ?? [];
|
|
131
|
+
const payload = {
|
|
132
|
+
schema_version: 1,
|
|
133
|
+
...(title ? { title } : {}),
|
|
134
|
+
...(base ? { base } : {}),
|
|
135
|
+
...(delivery ? { delivery } : {}),
|
|
136
|
+
...(gate.kind === "block" ? { dream_report: gate.block } : {}),
|
|
137
|
+
prose: input.prose,
|
|
138
|
+
roadmap,
|
|
139
|
+
};
|
|
140
|
+
const content = `${JSON.stringify(payload, null, 2)}\n`;
|
|
141
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
142
|
+
|
|
143
|
+
const written = deps.session.writeArtifact(OBJECTIVE_DRAFT_ARTIFACT, content);
|
|
144
|
+
switch (written.status) {
|
|
145
|
+
case "applied":
|
|
146
|
+
return { status: "revised", receipt: written.receipt, bytes, roadmapNodes: roadmap.length };
|
|
147
|
+
case "unchanged":
|
|
148
|
+
return {
|
|
149
|
+
status: "unchanged",
|
|
150
|
+
receipt: written.receipt,
|
|
151
|
+
bytes,
|
|
152
|
+
roadmapNodes: roadmap.length,
|
|
153
|
+
};
|
|
154
|
+
case "rejected":
|
|
155
|
+
return {
|
|
156
|
+
status: "rejected",
|
|
157
|
+
reason: "write_refused",
|
|
158
|
+
problem: `could not write the ${OBJECTIVE_DRAFT_ARTIFACT} artifact (see warnings)`,
|
|
159
|
+
errorType: "write_failed",
|
|
160
|
+
};
|
|
161
|
+
case "unverified":
|
|
162
|
+
return {
|
|
163
|
+
status: "unverified",
|
|
164
|
+
problem: `could not write the ${OBJECTIVE_DRAFT_ARTIFACT} artifact (see warnings)`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ------------------------------------------------------------------- the reader + the renderer
|
|
170
|
+
|
|
171
|
+
/** The validated working-objective draft shape consumers receive from `resumeObjectiveDraft`. */
|
|
172
|
+
export interface ObjectiveDraft {
|
|
173
|
+
title?: string;
|
|
174
|
+
prose: string;
|
|
175
|
+
roadmap: unknown[];
|
|
176
|
+
/** The objective's target branch; kept only when a non-blank string in the artifact. */
|
|
177
|
+
base?: string;
|
|
178
|
+
/** The reviewed delivery choice; kept only when exactly the enum (junk → absent, like `base`). */
|
|
179
|
+
delivery?: DeliveryChoice;
|
|
180
|
+
/** The dream-report block (§8.63); a present-but-malformed block refuses the WHOLE draft. */
|
|
181
|
+
dream_report?: ObjectiveDreamReportBlock;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The classified resume outcome: a refused draft is a fail-closed STOP at every consumer —
|
|
186
|
+
* it never takes the no-draft fallbacks' side effects (gate exit, driven turn). `raw` is the
|
|
187
|
+
* artifact bytes the draft was decoded from — the reviewed-bytes baseline; consumers that
|
|
188
|
+
* render and fence must use this one read for both (a second read could be newer than what
|
|
189
|
+
* the human saw).
|
|
190
|
+
*/
|
|
191
|
+
export type ResumeObjectiveDraftResult =
|
|
192
|
+
| { kind: "valid"; draft: ObjectiveDraft; raw: string }
|
|
193
|
+
| { kind: "absent" }
|
|
194
|
+
| { kind: "refused"; problem: string };
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Resume + validate the working-objective draft artifact from the session, classified: seam
|
|
198
|
+
* `absent` → `absent` (the genuine no-draft arm); seam `invalid` → `refused` carrying the
|
|
199
|
+
* seam's problem (a corrupted artifact is truthfully rendered at the edge — the seam's own
|
|
200
|
+
* stderr tier is untouched); decoder refusals (`decodeObjectiveDraft`) pass through. Never
|
|
201
|
+
* throws.
|
|
202
|
+
*/
|
|
203
|
+
export function resumeObjectiveDraft(session: WorkflowSession): ResumeObjectiveDraftResult {
|
|
204
|
+
const read = session.readArtifact(OBJECTIVE_DRAFT_ARTIFACT);
|
|
205
|
+
if (read.status === "absent") return { kind: "absent" };
|
|
206
|
+
if (read.status === "invalid") return { kind: "refused", problem: read.problem };
|
|
207
|
+
return decodeObjectiveDraft(read.content);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Decode + validate working-objective artifact bytes (the content half of
|
|
212
|
+
* `resumeObjectiveDraft`, for consumers that already hold the seam-validated bytes — e.g. the
|
|
213
|
+
* browser door, whose stale-guard baseline and decode input must be the SAME read): malformed
|
|
214
|
+
* JSON, a non-object payload, an unsupported `schema_version`, or blank prose → `refused`
|
|
215
|
+
* with the decoder's problem. `roadmap` defaults to `[]` when absent/non-array; `title` is
|
|
216
|
+
* kept only when a non-blank string. A present-but-malformed `dream_report` block refuses the
|
|
217
|
+
* WHOLE draft — deliberately stricter than the lenient junk→absent handling of
|
|
218
|
+
* `base`/`delivery`, because silently dropping a malformed report is exactly what §8.63
|
|
219
|
+
* forbids. Never throws.
|
|
220
|
+
*/
|
|
221
|
+
export function decodeObjectiveDraft(
|
|
222
|
+
content: string,
|
|
223
|
+
): Exclude<ResumeObjectiveDraftResult, { kind: "absent" }> {
|
|
224
|
+
const refuse = (why: string): { kind: "refused"; problem: string } => ({
|
|
225
|
+
kind: "refused",
|
|
226
|
+
problem: `${OBJECTIVE_DRAFT_ARTIFACT} ${why} — refusing the draft`,
|
|
227
|
+
});
|
|
228
|
+
let parsed: unknown;
|
|
229
|
+
try {
|
|
230
|
+
parsed = JSON.parse(content);
|
|
231
|
+
} catch {
|
|
232
|
+
return refuse("is not valid JSON");
|
|
233
|
+
}
|
|
234
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
235
|
+
return refuse("is not a JSON object");
|
|
236
|
+
}
|
|
237
|
+
const payload = parsed as Record<string, unknown>;
|
|
238
|
+
if (payload.schema_version !== 1) {
|
|
239
|
+
return refuse(`has an unsupported schema_version (${JSON.stringify(payload.schema_version)})`);
|
|
240
|
+
}
|
|
241
|
+
const prose = payload.prose;
|
|
242
|
+
if (typeof prose !== "string" || !prose.trim()) {
|
|
243
|
+
return refuse("has no prose");
|
|
244
|
+
}
|
|
245
|
+
const roadmap = Array.isArray(payload.roadmap) ? payload.roadmap : [];
|
|
246
|
+
const title =
|
|
247
|
+
typeof payload.title === "string" && payload.title.trim() ? payload.title : undefined;
|
|
248
|
+
const base = typeof payload.base === "string" && payload.base.trim() ? payload.base : undefined;
|
|
249
|
+
const delivery =
|
|
250
|
+
typeof payload.delivery === "string" && isDeliveryChoice(payload.delivery)
|
|
251
|
+
? payload.delivery
|
|
252
|
+
: undefined;
|
|
253
|
+
let dreamReport: ObjectiveDreamReportBlock | undefined;
|
|
254
|
+
if ("dream_report" in payload) {
|
|
255
|
+
const block = decodeDreamReportBlock(payload.dream_report);
|
|
256
|
+
if (block === null) {
|
|
257
|
+
return refuse("carries a malformed dream_report block");
|
|
258
|
+
}
|
|
259
|
+
dreamReport = block;
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
kind: "valid",
|
|
263
|
+
draft: {
|
|
264
|
+
...(title !== undefined ? { title } : {}),
|
|
265
|
+
...(base !== undefined ? { base } : {}),
|
|
266
|
+
...(delivery !== undefined ? { delivery } : {}),
|
|
267
|
+
...(dreamReport !== undefined ? { dream_report: dreamReport } : {}),
|
|
268
|
+
prose,
|
|
269
|
+
roadmap,
|
|
270
|
+
},
|
|
271
|
+
raw: content,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Sanitize a table cell: `|` escaped, newlines collapsed to a single space. */
|
|
276
|
+
function tableCell(value: string): string {
|
|
277
|
+
return value.replace(/\r?\n/g, " ").replace(/\|/g, "\\|");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Read a string field off an unknown-shaped roadmap node (`""` when absent/mistyped). */
|
|
281
|
+
function nodeString(node: unknown, key: string): string {
|
|
282
|
+
if (typeof node !== "object" || node === null) return "";
|
|
283
|
+
const value = (node as Record<string, unknown>)[key];
|
|
284
|
+
return typeof value === "string" ? value : "";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Render a node's `depends_on` as a `", "`-join of its string members; `-` when empty/absent. */
|
|
288
|
+
function nodeDependsOn(node: unknown): string {
|
|
289
|
+
if (typeof node !== "object" || node === null) return "-";
|
|
290
|
+
const value = (node as Record<string, unknown>).depends_on;
|
|
291
|
+
if (!Array.isArray(value)) return "-";
|
|
292
|
+
const deps = value.filter((d): d is string => typeof d === "string");
|
|
293
|
+
return deps.length > 0 ? deps.join(", ") : "-";
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** The always-present prominent `**Delivery:**` review line (contracts §8.45). */
|
|
297
|
+
function deliveryLine(draft: ObjectiveDraft): string {
|
|
298
|
+
if (draft.delivery === "stacked") {
|
|
299
|
+
return (
|
|
300
|
+
"**Delivery: STACKED** — all non-skipped roadmap nodes land as ONE atomic pull-request " +
|
|
301
|
+
"train (capability-checked at save)"
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
return "**Delivery: incremental** (the default — each plan lands independently)";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Render the draft as the markdown review surface (JSON is storage/transport only — contracts
|
|
309
|
+
* §8.1): the optional `# title` heading, the prose verbatim, and (when the roadmap is non-empty)
|
|
310
|
+
* a `## Roadmap` section with ONE markdown table. A prominent `**Delivery:**` line renders
|
|
311
|
+
* directly under the title unconditionally (the reviewed choice must be visible either way —
|
|
312
|
+
* contracts §8.45). The `Phase` column appears only when some node carries a non-blank string
|
|
313
|
+
* `phase`. When the draft carries a `dream_report` block, the stored CANONICAL parts append as
|
|
314
|
+
* the final section — the review surface IS the approval bundle: the objective and its report
|
|
315
|
+
* review (and are approved or denied) together (§8.63); the parts carry their own
|
|
316
|
+
* `# Dream report — <run_id>` headers. Pure; never throws.
|
|
317
|
+
*/
|
|
318
|
+
export function renderObjectiveDraft(draft: ObjectiveDraft): string {
|
|
319
|
+
let out = "";
|
|
320
|
+
if (draft.title) out += `# ${draft.title}\n\n`;
|
|
321
|
+
out += `${deliveryLine(draft)}\n\n`;
|
|
322
|
+
out += draft.prose;
|
|
323
|
+
|
|
324
|
+
if (draft.roadmap.length > 0) {
|
|
325
|
+
const withPhase = draft.roadmap.some((node) => nodeString(node, "phase").trim().length > 0);
|
|
326
|
+
const header = withPhase
|
|
327
|
+
? "| Node | Phase | Description | Depends On | Status |\n| --- | --- | --- | --- | --- |"
|
|
328
|
+
: "| Node | Description | Depends On | Status |\n| --- | --- | --- | --- |";
|
|
329
|
+
const rows = draft.roadmap.map((node) => {
|
|
330
|
+
const cells = [
|
|
331
|
+
tableCell(nodeString(node, "id")),
|
|
332
|
+
...(withPhase ? [tableCell(nodeString(node, "phase"))] : []),
|
|
333
|
+
tableCell(nodeString(node, "description")),
|
|
334
|
+
tableCell(nodeDependsOn(node)),
|
|
335
|
+
tableCell(nodeString(node, "status") || "pending"),
|
|
336
|
+
];
|
|
337
|
+
return `| ${cells.join(" | ")} |`;
|
|
338
|
+
});
|
|
339
|
+
out = `${out.trimEnd()}\n\n## Roadmap\n\n${header}\n${rows.join("\n")}\n`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (draft.dream_report === undefined) return out;
|
|
343
|
+
// The approval bundle: objective first, then the stored CANONICAL report parts.
|
|
344
|
+
return `${out.trimEnd()}\n\n${draft.dream_report.parts.join("\n\n")}\n`;
|
|
345
|
+
}
|
package/extension/{factories/objectiveDreamReport.ts → authoring/objective/dreamReportGate.ts}
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// The `dream_report` gate for the objective draft/review/save path (contracts.md §8.63).
|
|
2
2
|
//
|
|
3
|
-
// ONE resolver implements the whole gate matrix — `
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// like `run_dream_wave` (
|
|
3
|
+
// ONE resolver implements the whole gate matrix — `reviseObjectiveDraft` (draft.ts) and
|
|
4
|
+
// `saveObjective` (save.ts) both consume its typed outcome, so no parallel branch/message
|
|
5
|
+
// implementation can drift. "Dream session" is detected structurally, exactly
|
|
6
|
+
// like `run_dream_wave` (pi/v1/learning/dream.ts): the session's claimed `run_id` plus the
|
|
7
7
|
// existence of the run-scoped dream manifest (no claimed run counts as non-dream). The gate is
|
|
8
8
|
// fail-closed in BOTH directions: a dream session refuses a report-less objective (the
|
|
9
9
|
// objective and its report review as ONE bundle — an approval is always savable), and a
|
|
@@ -13,28 +13,25 @@
|
|
|
13
13
|
// Trusted-context recovery follows the session-artifacts digest-pointer doctrine: the bare
|
|
14
14
|
// run-scratch bundle is never trusted — the `dream_bundle_digest` workflow-state marker
|
|
15
15
|
// (cleared at wave entry, set to the finalized bytes' digest after a successful finalize) is
|
|
16
|
-
// the freshness/integrity authority, and the bundle is strictly re-decoded through
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
16
|
+
// the freshness/integrity authority, and the bundle is strictly re-decoded through the
|
|
17
|
+
// finalized-bundle decoder on every recovery read (untrusted-at-rest posture). The recovery
|
|
18
|
+
// MECHANICS are edge-owned: the resolver consumes the runtime-minted `DreamGateRecovery`
|
|
19
|
+
// capability (production: `pi/v1/objectiveDreamGate.ts`), so this module stays storage-free.
|
|
20
|
+
// After a successful recovery the revalidation bracket (contracts.md §8.65) re-proves
|
|
21
|
+
// HEAD-unchanged + tree-clean against the manifest's stamped `commit_sha` — at draft-write AND
|
|
22
|
+
// save, since both consumers flow through the one resolver; drift refuses `bad_state` (the
|
|
23
|
+
// analysis is stale).
|
|
21
24
|
//
|
|
22
|
-
// Imports only the dream wave siblings
|
|
23
|
-
//
|
|
25
|
+
// Imports only the dream wave siblings — cycle-free (nothing in `waves/` imports `authoring/`)
|
|
26
|
+
// and loadable under `node --test`.
|
|
24
27
|
|
|
25
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
26
|
-
import { dirname, join } from "node:path";
|
|
27
|
-
import { runScratchDir } from "../substrate/cache.ts";
|
|
28
|
-
import { revalidationBracket } from "../substrate/git.ts";
|
|
29
|
-
import { digestSessionData, type SessionDataCtx } from "../substrate/sessionData.ts";
|
|
30
|
-
import { branchOf, rebuildWorkflowState, type WorkflowState } from "../substrate/workflowState.ts";
|
|
31
|
-
import { DREAM_ANALYSES_FILENAME, decodeFinalizedDreamBundle } from "../waves/dreamReducerWave.ts";
|
|
32
|
-
import { buildDreamReport, type DreamReportContext } from "../waves/dreamReport.ts";
|
|
33
28
|
import {
|
|
34
29
|
codePointLength,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} from "
|
|
30
|
+
type DreamLaneAnalysis,
|
|
31
|
+
type DreamManifest,
|
|
32
|
+
} from "../../learning/dream.ts";
|
|
33
|
+
import type { DreamReducerAnalysis } from "../../learning/dreamReducer.ts";
|
|
34
|
+
import { buildDreamReport, type DreamReportContext } from "../../learning/dreamReport.ts";
|
|
38
35
|
|
|
39
36
|
/**
|
|
40
37
|
* The shared part-invariance + size rule's comment-body cap (contracts §8.64) — the full
|
|
@@ -152,91 +149,35 @@ export function decodeDreamReportBlock(value: unknown): ObjectiveDreamReportBloc
|
|
|
152
149
|
}
|
|
153
150
|
|
|
154
151
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* 2. the freshness check: the `dream_bundle_digest` marker (read from the caller's ONE
|
|
162
|
-
* workflow-state snapshot) must be present, non-empty, and equal the digest of the bundle
|
|
163
|
-
* bytes just read — a bare file is never trusted (missing marker = no finalized wave;
|
|
164
|
-
* empty = invalidated by a newer attempt, including a cleanup-failure residue; mismatch =
|
|
165
|
-
* stale/tampered bytes);
|
|
166
|
-
* 3. the strict finalized decode (`decodeFinalizedDreamBundle` over the digest of the
|
|
167
|
-
* manifest bytes just read — the marker authenticates the bundle bytes and the bundle's
|
|
168
|
-
* `manifest_digest` extends that authority to the manifest, so an at-rest manifest edit
|
|
169
|
-
* refuses; the analyses-only mid-wave shape refuses here too).
|
|
152
|
+
* The narrow per-operation trusted-context recovery capability the Pi/session edge mints
|
|
153
|
+
* (production: `productionDreamGateRecovery` in `pi/v1/objectiveDreamGate.ts`; tests inject
|
|
154
|
+
* fakes). ANTI-PROOF-OBJECT CONTRACT: the decode/digest/revalidation checks behind
|
|
155
|
+
* `recoverContext` and `bracket` are runtime verification executed on EVERY consuming
|
|
156
|
+
* operation (draft-write and save), never replaced by a structural type, assertion, or
|
|
157
|
+
* previously computed proof object.
|
|
170
158
|
*/
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
let bundleBytes: string;
|
|
194
|
-
try {
|
|
195
|
-
bundleBytes = readFileSync(bundlePath, "utf8");
|
|
196
|
-
} catch {
|
|
197
|
-
return {
|
|
198
|
-
ok: false,
|
|
199
|
-
detail: `no dream bundle at '${bundlePath}' — re-run the dream wave`,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
if (marker === undefined || marker === "") {
|
|
203
|
-
return {
|
|
204
|
-
ok: false,
|
|
205
|
-
detail: "no finalized dream wave for this session — re-run the dream wave",
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
if (digestSessionData(bundleBytes) !== marker) {
|
|
209
|
-
return {
|
|
210
|
-
ok: false,
|
|
211
|
-
detail:
|
|
212
|
-
"the dream bundle does not match the session's finalized digest — re-run the dream wave",
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
let rawBundle: unknown;
|
|
216
|
-
try {
|
|
217
|
-
rawBundle = JSON.parse(bundleBytes);
|
|
218
|
-
} catch (error) {
|
|
219
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
220
|
-
return { ok: false, detail: `dream bundle is not valid JSON: ${detail}` };
|
|
221
|
-
}
|
|
222
|
-
const decoded = decodeFinalizedDreamBundle(
|
|
223
|
-
rawBundle,
|
|
224
|
-
manifest.manifest,
|
|
225
|
-
digestSessionData(manifestBytes),
|
|
226
|
-
);
|
|
227
|
-
if (!decoded.ok) {
|
|
228
|
-
return { ok: false, detail: `${decoded.detail} — re-run the dream wave` };
|
|
229
|
-
}
|
|
230
|
-
return {
|
|
231
|
-
ok: true,
|
|
232
|
-
context: {
|
|
233
|
-
manifest: manifest.manifest,
|
|
234
|
-
analyses: decoded.analyses,
|
|
235
|
-
reducers: decoded.reducers,
|
|
236
|
-
run_id: runId,
|
|
237
|
-
generated_at: generatedAt,
|
|
238
|
-
},
|
|
239
|
-
};
|
|
159
|
+
export interface DreamGateRecovery {
|
|
160
|
+
/** ONE fresh workflow-state snapshot per gate resolution (run identity + freshness marker + dream detection).
|
|
161
|
+
* `detail` is always the RAW CAUSE (a caught message or a fixed cause literal) — the resolver
|
|
162
|
+
* owns the one rendering prefix; the capability never pre-renders. */
|
|
163
|
+
readSession():
|
|
164
|
+
| { kind: "unreadable"; detail: string }
|
|
165
|
+
| { kind: "read"; runId: string | null; dream: boolean; marker: string | undefined };
|
|
166
|
+
/** Fresh manifest+bundle read + the full decode/digest ladder — re-executed on EVERY call, never cached.
|
|
167
|
+
* `detail` here IS the final text (today's recovery details are complete sentences; byte-preserved). */
|
|
168
|
+
recoverContext(
|
|
169
|
+
runId: string,
|
|
170
|
+
marker: string | undefined,
|
|
171
|
+
):
|
|
172
|
+
| {
|
|
173
|
+
ok: true;
|
|
174
|
+
manifest: DreamManifest;
|
|
175
|
+
analyses: DreamLaneAnalysis[];
|
|
176
|
+
reducers: DreamReducerAnalysis[];
|
|
177
|
+
}
|
|
178
|
+
| { ok: false; detail: string };
|
|
179
|
+
/** The §8.65 revalidation bracket against the recovered manifest's stamped commit_sha. */
|
|
180
|
+
bracket(expectedSha: string): { ok: boolean; detail: string | null };
|
|
240
181
|
}
|
|
241
182
|
|
|
242
183
|
/** The typed gate outcome both consumers branch on — the whole matrix, one vocabulary. */
|
|
@@ -257,40 +198,34 @@ export type DreamReportGateOutcome =
|
|
|
257
198
|
* | dream | absent | refuse `invalid_input` (one approval bundle) |
|
|
258
199
|
* | dream | present | recover context → `buildDreamReport` → refuse or `block` |
|
|
259
200
|
*
|
|
260
|
-
* An UNREADABLE workflow state (
|
|
261
|
-
*
|
|
201
|
+
* An UNREADABLE workflow state (`readSession()`'s `unreadable` arm — a throwing branch read,
|
|
202
|
+
* or the capability's fail-closed run-id/marker narrowing) refuses `bad_state` BEFORE the
|
|
203
|
+
* matrix — it is never conflated with a confirmed non-dream session (the `activeSessionRunId`
|
|
262
204
|
* null-on-throw sentinel would otherwise let a transient read failure surface as `absent`).
|
|
205
|
+
* The capability's `detail` is the RAW CAUSE; this resolver owns the one rendering prefix.
|
|
263
206
|
*
|
|
264
207
|
* Failure taxonomy: gate violations + `buildDreamReport` refusals → `invalid_input` (the
|
|
265
208
|
* bounded ≤25 named details newline-joined); an unreadable workflow state and
|
|
266
|
-
* context-recovery failures → `bad_state
|
|
209
|
+
* context-recovery failures → `bad_state` (recovery details pass through UNPREFIXED).
|
|
267
210
|
*/
|
|
268
211
|
export function resolveDreamReportGate(
|
|
269
|
-
|
|
212
|
+
recovery: DreamGateRecovery,
|
|
270
213
|
input: unknown,
|
|
271
214
|
generatedAt: string,
|
|
272
|
-
bracket: (
|
|
273
|
-
cwd: string,
|
|
274
|
-
expectedSha: string,
|
|
275
|
-
) => { ok: boolean; detail: string | null } = revalidationBracket,
|
|
276
215
|
): DreamReportGateOutcome {
|
|
277
|
-
// ONE workflow-state snapshot for the whole gate (run identity + the freshness marker
|
|
278
|
-
// read with error distinction: unreadable state fails closed, never
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
} catch (error) {
|
|
283
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
216
|
+
// ONE workflow-state snapshot for the whole gate (run identity + the freshness marker +
|
|
217
|
+
// dream detection), read with error distinction: unreadable state fails closed, never
|
|
218
|
+
// "non-dream".
|
|
219
|
+
const session = recovery.readSession();
|
|
220
|
+
if (session.kind === "unreadable") {
|
|
284
221
|
return {
|
|
285
222
|
kind: "refuse",
|
|
286
223
|
errorType: "bad_state",
|
|
287
|
-
detail: `session workflow state is unreadable — cannot resolve the dream_report gate: ${detail}`,
|
|
224
|
+
detail: `session workflow state is unreadable — cannot resolve the dream_report gate: ${session.detail}`,
|
|
288
225
|
};
|
|
289
226
|
}
|
|
290
|
-
const runId =
|
|
291
|
-
|
|
292
|
-
runId !== null && existsSync(join(runScratchDir(ctx.cwd, runId), DREAM_MANIFEST_FILENAME));
|
|
293
|
-
if (runId === null || !dream) {
|
|
227
|
+
const runId = session.runId;
|
|
228
|
+
if (runId === null || !session.dream) {
|
|
294
229
|
if (input === undefined) return { kind: "absent" };
|
|
295
230
|
return {
|
|
296
231
|
kind: "refuse",
|
|
@@ -309,17 +244,25 @@ export function resolveDreamReportGate(
|
|
|
309
244
|
"report review as one bundle",
|
|
310
245
|
};
|
|
311
246
|
}
|
|
312
|
-
|
|
247
|
+
// The marker rides the SAME snapshot as the run identity (the one-snapshot rule); the
|
|
248
|
+
// capability re-executes the full read+decode+digest ladder on every call, never caching.
|
|
249
|
+
const recovered = recovery.recoverContext(runId, session.marker);
|
|
313
250
|
if (!recovered.ok) {
|
|
314
251
|
return { kind: "refuse", errorType: "bad_state", detail: recovered.detail };
|
|
315
252
|
}
|
|
253
|
+
const context: DreamReportContext = {
|
|
254
|
+
manifest: recovered.manifest,
|
|
255
|
+
analyses: recovered.analyses,
|
|
256
|
+
reducers: recovered.reducers,
|
|
257
|
+
run_id: runId,
|
|
258
|
+
generated_at: generatedAt,
|
|
259
|
+
};
|
|
316
260
|
// The revalidation-bracket re-check (contracts.md §8.65): the manifest — with its stamped
|
|
317
261
|
// commit_sha — is now decoded and authenticated, so re-prove HEAD-unchanged + tree-clean
|
|
318
262
|
// against it. Both `writeObjectiveDraft` and `saveObjective` flow through this resolver, so
|
|
319
263
|
// the bracket re-fires at draft-write AND save; non-dream paths never reach it (the matrix
|
|
320
|
-
// above returned already).
|
|
321
|
-
|
|
322
|
-
const drift = bracket(ctx.cwd, recovered.context.manifest.commit_sha);
|
|
264
|
+
// above returned already).
|
|
265
|
+
const drift = recovery.bracket(context.manifest.commit_sha);
|
|
323
266
|
if (!drift.ok) {
|
|
324
267
|
return {
|
|
325
268
|
kind: "refuse",
|
|
@@ -329,7 +272,7 @@ export function resolveDreamReportGate(
|
|
|
329
272
|
"stale; re-run perk learn dream",
|
|
330
273
|
};
|
|
331
274
|
}
|
|
332
|
-
const built = buildDreamReport(input,
|
|
275
|
+
const built = buildDreamReport(input, context);
|
|
333
276
|
if (!built.ok) {
|
|
334
277
|
return { kind: "refuse", errorType: "invalid_input", detail: built.details.join("\n") };
|
|
335
278
|
}
|