@sayknow-cli/coding-agent 0.3.7 → 0.3.8
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/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/capability/rule.d.ts +8 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +1 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +7 -0
- package/dist/types/cli/update-cli.d.ts +22 -0
- package/dist/types/cli.d.ts +124 -1
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +7 -6
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +6 -0
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +105 -1
- package/dist/types/config/settings.d.ts +8 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -3
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/hooks/native-skill-hook.d.ts +12 -0
- package/dist/types/main.d.ts +4 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +9 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config.d.ts +14 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +6 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
- package/dist/types/notifications/telegram-daemon.d.ts +23 -3
- package/dist/types/notifications/telegram-reference.d.ts +0 -5
- package/dist/types/notifications/threaded-render.d.ts +6 -0
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +61 -6
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +17 -1
- package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/skc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/skc-runtime/deep-interview-state.d.ts +17 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +7 -0
- package/dist/types/skc-runtime/state-writer.d.ts +3 -0
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +120 -13
- package/dist/types/skill-state/active-state.d.ts +18 -1
- package/dist/types/system-prompt.d.ts +18 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/dist/types/utils/file-mentions.d.ts +14 -0
- package/dist/types/utils/git.d.ts +32 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/package.json +8 -8
- package/src/append-only-mode.ts +58 -0
- package/src/autoresearch/prompt.md +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/capability/rule.ts +9 -0
- package/src/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +61 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/fixture-report.test.ts +230 -0
- package/src/cli/fixture-report.ts +171 -0
- package/src/cli/nofile-limit.ts +48 -0
- package/src/cli/notify-cli.ts +92 -8
- package/src/cli/telegram-cli.ts +1 -1
- package/src/cli/update-cli.ts +85 -9
- package/src/cli.ts +29 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/commands/deep-interview.ts +2 -1
- package/src/config/file-lock.ts +73 -34
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-profiles.ts +31 -2
- package/src/config/settings-schema.ts +68 -1
- package/src/config/settings.ts +22 -0
- package/src/defaults/skc/skills/team/SKILL.md +17 -14
- package/src/defaults/skc/skills/ultragoal/SKILL.md +59 -4
- package/src/defaults/skc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
- package/src/defaults/skc-defaults.ts +9 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/index.ts +1 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +60 -2
- package/src/export/ttsr.ts +96 -18
- package/src/extensibility/extensions/runner.ts +183 -219
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +156 -44
- package/src/goals/runtime.ts +10 -2
- package/src/hindsight/backend.ts +1 -3
- package/src/hooks/native-skill-hook.ts +124 -4
- package/src/internal-urls/docs-index.generated.ts +14 -5
- package/src/main.ts +95 -43
- package/src/memories/index.ts +12 -1
- package/src/modes/components/assistant-message.ts +241 -82
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/model-selector.ts +54 -11
- package/src/modes/components/queued-message-selector.ts +92 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/tool-execution.ts +44 -19
- package/src/modes/components/welcome.ts +181 -44
- package/src/modes/controllers/command-controller.ts +40 -4
- package/src/modes/controllers/event-controller.ts +66 -47
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +180 -18
- package/src/modes/controllers/selector-controller.ts +127 -24
- package/src/modes/interactive-mode.ts +51 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +21 -20
- package/src/modes/rpc/rpc-mode.ts +29 -3
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/shared/agent-wire/command-validation.ts +4 -0
- package/src/modes/shared/agent-wire/event-contract.ts +22 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +20 -7
- package/src/notifications/config.ts +25 -5
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +117 -14
- package/src/notifications/lifecycle-commands.ts +37 -7
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/telegram-daemon-control.ts +5 -3
- package/src/notifications/telegram-daemon.ts +302 -51
- package/src/notifications/telegram-reference.ts +0 -16
- package/src/notifications/threaded-render.ts +17 -1
- package/src/prompts/agents/architect.md +19 -10
- package/src/prompts/agents/critic.md +16 -18
- package/src/prompts/agents/executor.md +14 -11
- package/src/prompts/agents/planner.md +18 -13
- package/src/prompts/ci-green-request.md +3 -8
- package/src/prompts/goals/goal-continuation.md +0 -5
- package/src/prompts/goals/goal-mode-active.md +0 -5
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +3 -3
- package/src/prompts/system/project-prompt.md +0 -13
- package/src/prompts/system/subagent-system-prompt.md +3 -2
- package/src/prompts/system/system-prompt.md +29 -17
- package/src/prompts/system/volatile-project-context.md +10 -0
- package/src/prompts/system/web-search.md +4 -7
- package/src/prompts/tools/apply-patch.md +6 -0
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/bisect.md +22 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/computer.md +2 -2
- package/src/prompts/tools/cron.md +0 -2
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/github.md +6 -6
- package/src/prompts/tools/goal.md +1 -3
- package/src/prompts/tools/image-gen.md +12 -5
- package/src/prompts/tools/irc.md +5 -4
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/monitor.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/replace.md +2 -21
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +1 -11
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/skill.md +3 -2
- package/src/prompts/tools/ssh.md +2 -1
- package/src/prompts/tools/subagent.md +2 -2
- package/src/prompts/tools/task.md +4 -5
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/vim.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/prompts/tools/write.md +8 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +52 -28
- package/src/session/agent-session.ts +1011 -141
- package/src/session/agent-storage.ts +4 -0
- package/src/session/file-mention-pruning.ts +64 -0
- package/src/session/messages.ts +4 -0
- package/src/session/session-manager.ts +103 -4
- package/src/skc-runtime/deep-interview-ambiguity.ts +206 -0
- package/src/skc-runtime/deep-interview-recorder.ts +62 -8
- package/src/skc-runtime/deep-interview-state.ts +44 -2
- package/src/skc-runtime/session-state-sidecar.ts +263 -20
- package/src/skc-runtime/state-runtime.ts +12 -2
- package/src/skc-runtime/state-writer.ts +15 -0
- package/src/skc-runtime/ultragoal-guard.ts +55 -66
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/skc-runtime/ultragoal-runtime.ts +1663 -159
- package/src/skc-runtime/workflow-manifest.generated.json +113 -2
- package/src/skc-runtime/workflow-manifest.ts +74 -1
- package/src/skill-state/active-state.ts +141 -5
- package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
- package/src/slash-commands/builtin-registry.ts +218 -71
- package/src/system-prompt.ts +57 -16
- package/src/task/executor.ts +132 -24
- package/src/task/fork-context-advisory.ts +2 -7
- package/src/task/fork-context-budget.ts +9 -0
- package/src/task/index.ts +13 -6
- package/src/task/output-manager.ts +20 -8
- package/src/task/receipt.ts +26 -7
- package/src/task/token-log.test.ts +188 -0
- package/src/task/token-log.ts +179 -0
- package/src/tools/bisect.ts +367 -0
- package/src/tools/computer.ts +246 -43
- package/src/tools/index.ts +3 -0
- package/src/tools/output-meta.ts +116 -4
- package/src/tools/search.ts +8 -2
- package/src/tools/skill.ts +15 -0
- package/src/tools/telegram-send.ts +6 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
- package/src/utils/file-mentions.ts +39 -7
- package/src/utils/git.ts +73 -0
- package/src/web/scrapers/youtube.ts +24 -12
- package/src/workflow/workflow-intent-diff.ts +29 -6
- package/src/workflow/workflow-intent-report-consumer.ts +221 -0
- package/src/workflow/workflow-intent-report-schema.ts +129 -0
- package/src/workflow/workflow-intent-report.ts +135 -0
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
|
|
3
3
|
import { resolveSkcSessionForRead, SessionResolutionError } from "./session-resolution";
|
|
4
|
+
import { validateDeferredMemberReceiptFresh, validateReceiptFreshBase } from "./ultragoal-receipt-freshness";
|
|
4
5
|
import {
|
|
5
|
-
computeUltragoalPlanGeneration,
|
|
6
6
|
getUltragoalPaths,
|
|
7
7
|
getUltragoalRunCompletionState,
|
|
8
|
-
hashStructuredValue,
|
|
9
8
|
readUltragoalLedger,
|
|
10
9
|
readUltragoalPlan,
|
|
11
10
|
recordUltragoalNudgeIfBudgetRemaining,
|
|
12
11
|
resolveUltragoalNudgeBudget,
|
|
13
12
|
selectUltragoalNudgeTarget,
|
|
14
|
-
type UltragoalCompletionVerification,
|
|
15
13
|
type UltragoalGoal,
|
|
16
14
|
type UltragoalLedgerEvent,
|
|
17
15
|
type UltragoalNudgeSurface,
|
|
@@ -176,26 +174,6 @@ function findReceiptGoal(
|
|
|
176
174
|
return storyGoal ? { goal: storyGoal, receiptKind: "per-goal" } : null;
|
|
177
175
|
}
|
|
178
176
|
|
|
179
|
-
function findLedgerReceiptEvent(
|
|
180
|
-
ledger: readonly UltragoalLedgerEvent[],
|
|
181
|
-
receipt: UltragoalCompletionVerification,
|
|
182
|
-
): UltragoalLedgerEvent | null {
|
|
183
|
-
return (
|
|
184
|
-
ledger.find(event => {
|
|
185
|
-
if (event.eventId !== receipt.checkpointLedgerEventId) return false;
|
|
186
|
-
if (event.event !== "goal_checkpointed") return false;
|
|
187
|
-
if (event.goalId !== receipt.goalId) return false;
|
|
188
|
-
const eventReceipt = event.completionVerification as UltragoalCompletionVerification | undefined;
|
|
189
|
-
return (
|
|
190
|
-
event.status === "complete" &&
|
|
191
|
-
eventReceipt?.receiptId === receipt.receiptId &&
|
|
192
|
-
eventReceipt.receiptKind === receipt.receiptKind &&
|
|
193
|
-
eventReceipt.planGeneration === receipt.planGeneration
|
|
194
|
-
);
|
|
195
|
-
}) ?? null
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
177
|
export function validateCompletionReceipt(input: {
|
|
200
178
|
plan: UltragoalPlan;
|
|
201
179
|
ledger: readonly UltragoalLedgerEvent[];
|
|
@@ -210,55 +188,66 @@ export function validateCompletionReceipt(input: {
|
|
|
210
188
|
goalId: input.goal.id,
|
|
211
189
|
};
|
|
212
190
|
}
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
message: `Ultragoal ${input.goal.id} receipt is malformed or stale.`,
|
|
223
|
-
goalId: input.goal.id,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
const event = findLedgerReceiptEvent(input.ledger, receipt);
|
|
227
|
-
if (!event) {
|
|
228
|
-
return {
|
|
229
|
-
state: "active_stale_receipt",
|
|
230
|
-
message: `Ultragoal ${input.goal.id} receipt ledger event is missing.`,
|
|
231
|
-
goalId: input.goal.id,
|
|
232
|
-
};
|
|
191
|
+
if (receipt.validationBatch?.role === "deferred-member") {
|
|
192
|
+
return validateDeferredMemberReceiptFresh({
|
|
193
|
+
plan: input.plan,
|
|
194
|
+
ledger: input.ledger,
|
|
195
|
+
goal: input.goal,
|
|
196
|
+
receipt,
|
|
197
|
+
receiptKind: input.receiptKind,
|
|
198
|
+
requireClose: true,
|
|
199
|
+
});
|
|
233
200
|
}
|
|
234
|
-
const
|
|
201
|
+
const baseDiagnostic = validateReceiptFreshBase({
|
|
235
202
|
plan: input.plan,
|
|
236
203
|
ledger: input.ledger,
|
|
237
204
|
goal: input.goal,
|
|
205
|
+
receipt,
|
|
238
206
|
receiptKind: input.receiptKind,
|
|
239
|
-
beforeStatus: receipt.goalStatusBeforeCheckpoint,
|
|
240
|
-
excludeEventId: receipt.checkpointLedgerEventId,
|
|
241
207
|
});
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
208
|
+
if (baseDiagnostic) return baseDiagnostic;
|
|
209
|
+
if (receipt.validationBatch?.role === "batch-close") {
|
|
210
|
+
for (const memberId of receipt.validationBatch.memberIds) {
|
|
211
|
+
const member = input.plan.goals.find(goal => goal.id === memberId);
|
|
212
|
+
if (
|
|
213
|
+
!member?.validationBatch ||
|
|
214
|
+
member.validationBatch.metadataHash !== receipt.validationBatch.memberMetadataHashes[memberId]
|
|
215
|
+
) {
|
|
216
|
+
return {
|
|
217
|
+
state: "active_stale_receipt",
|
|
218
|
+
message: `Ultragoal ${input.goal.id} batch-close receipt has stale member metadata for ${memberId}.`,
|
|
219
|
+
goalId: input.goal.id,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (memberId === receipt.validationBatch.finalGoalId) continue;
|
|
223
|
+
const memberReceipt = member.completionVerification;
|
|
224
|
+
if (memberReceipt?.validationBatch?.role !== "deferred-member") {
|
|
225
|
+
return {
|
|
226
|
+
state: "active_missing_final_receipt",
|
|
227
|
+
message: `Ultragoal ${input.goal.id} batch-close receipt requires deferred member receipt for ${memberId}.`,
|
|
228
|
+
goalId: input.goal.id,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const memberDiagnostic = validateDeferredMemberReceiptFresh({
|
|
232
|
+
plan: input.plan,
|
|
233
|
+
ledger: input.ledger,
|
|
234
|
+
goal: member,
|
|
235
|
+
receipt: memberReceipt,
|
|
236
|
+
receiptKind: "per-goal",
|
|
237
|
+
requireClose: false,
|
|
238
|
+
});
|
|
239
|
+
if (memberDiagnostic.state !== "active_verified_complete") return memberDiagnostic;
|
|
240
|
+
if (
|
|
241
|
+
receipt.validationBatch.memberReceiptIds[memberId] !== memberReceipt.receiptId ||
|
|
242
|
+
receipt.validationBatch.memberChangeSetHashes[memberId] !== memberReceipt.validationBatch.changeSetHash
|
|
243
|
+
) {
|
|
244
|
+
return {
|
|
245
|
+
state: "active_stale_receipt",
|
|
246
|
+
message: `Ultragoal ${input.goal.id} batch-close receipt is stale for deferred member ${memberId}.`,
|
|
247
|
+
goalId: input.goal.id,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
262
251
|
}
|
|
263
252
|
if (input.receiptKind === "final-aggregate") {
|
|
264
253
|
const incomplete = requiredGoals(input.plan).filter(goal => goal.status !== "complete");
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import type {
|
|
3
|
+
UltragoalCompletionVerification,
|
|
4
|
+
UltragoalGoal,
|
|
5
|
+
UltragoalGoalStatus,
|
|
6
|
+
UltragoalLedgerEvent,
|
|
7
|
+
UltragoalPlan,
|
|
8
|
+
UltragoalReceiptKind,
|
|
9
|
+
} from "./ultragoal-runtime";
|
|
10
|
+
|
|
11
|
+
export type UltragoalReceiptFreshnessDiagnostic = {
|
|
12
|
+
state:
|
|
13
|
+
| "inactive"
|
|
14
|
+
| "unrelated_goal"
|
|
15
|
+
| "active_verified_complete"
|
|
16
|
+
| "active_missing_receipt"
|
|
17
|
+
| "active_stale_receipt"
|
|
18
|
+
| "active_missing_final_receipt"
|
|
19
|
+
| "active_dirty_quality_gate"
|
|
20
|
+
| "active_review_blocked_unrecorded"
|
|
21
|
+
| "active_review_blocked_recorded"
|
|
22
|
+
| "unreadable_fail_closed";
|
|
23
|
+
message: string;
|
|
24
|
+
goalId?: string;
|
|
25
|
+
};
|
|
26
|
+
function stableStructuredValue(value: unknown): unknown {
|
|
27
|
+
if (Array.isArray(value)) return value.map(item => stableStructuredValue(item));
|
|
28
|
+
if (typeof value !== "object" || value === null) return value;
|
|
29
|
+
const record = value as Record<string, unknown>;
|
|
30
|
+
const sorted: Record<string, unknown> = {};
|
|
31
|
+
for (const key of Object.keys(record).sort()) {
|
|
32
|
+
const item = record[key];
|
|
33
|
+
if (item !== undefined) sorted[key] = stableStructuredValue(item);
|
|
34
|
+
}
|
|
35
|
+
return sorted;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hashStructuredValue(value: unknown): string {
|
|
39
|
+
return crypto
|
|
40
|
+
.createHash("sha256")
|
|
41
|
+
.update(JSON.stringify(stableStructuredValue(value)))
|
|
42
|
+
.digest("hex");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function requiredUltragoalGoals(plan: UltragoalPlan): UltragoalGoal[] {
|
|
46
|
+
return plan.goals.filter(goal => goal.status !== "superseded");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function receiptRelevantGoals(
|
|
50
|
+
plan: UltragoalPlan,
|
|
51
|
+
goal: UltragoalGoal,
|
|
52
|
+
receiptKind: UltragoalReceiptKind,
|
|
53
|
+
): UltragoalGoal[] {
|
|
54
|
+
if (goal.validationBatch?.finalGoalId === goal.id) {
|
|
55
|
+
return goal.validationBatch.memberIds.map(memberId => {
|
|
56
|
+
const member = plan.goals.find(item => item.id === memberId);
|
|
57
|
+
if (!member)
|
|
58
|
+
throw new Error(`validation batch ${goal.validationBatch?.batchId} references missing goal ${memberId}`);
|
|
59
|
+
return member;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return receiptKind === "final-aggregate" ? requiredUltragoalGoals(plan) : [goal];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function ledgerEventId(event: UltragoalLedgerEvent): string | null {
|
|
66
|
+
return typeof event.eventId === "string" && event.eventId.trim().length > 0 ? event.eventId : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isReceiptFreshnessBookkeepingEvent(event: UltragoalLedgerEvent): boolean {
|
|
70
|
+
return event.event === "nudge";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function latestRelevantLedgerEventId(
|
|
74
|
+
ledger: readonly UltragoalLedgerEvent[],
|
|
75
|
+
relevantGoalIds: readonly string[],
|
|
76
|
+
excludeEventId?: string,
|
|
77
|
+
): string | null {
|
|
78
|
+
const relevant = new Set(relevantGoalIds);
|
|
79
|
+
for (const event of [...ledger].reverse()) {
|
|
80
|
+
const eventId = ledgerEventId(event);
|
|
81
|
+
if (eventId && eventId === excludeEventId) continue;
|
|
82
|
+
if (isReceiptFreshnessBookkeepingEvent(event)) continue;
|
|
83
|
+
const goalId = typeof event.goalId === "string" ? event.goalId.trim() : "";
|
|
84
|
+
if (goalId && relevant.has(goalId)) return eventId;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function planSnapshotForReceipt(input: {
|
|
90
|
+
plan: UltragoalPlan;
|
|
91
|
+
goal: UltragoalGoal;
|
|
92
|
+
beforeStatus: UltragoalGoalStatus;
|
|
93
|
+
targetGoalUpdatedAt: string;
|
|
94
|
+
receiptKind: UltragoalReceiptKind;
|
|
95
|
+
}): unknown {
|
|
96
|
+
const targetGoalSnapshot = {
|
|
97
|
+
...input.goal,
|
|
98
|
+
status: input.beforeStatus,
|
|
99
|
+
updatedAt: input.targetGoalUpdatedAt,
|
|
100
|
+
evidence: undefined,
|
|
101
|
+
completedAt: undefined,
|
|
102
|
+
completionVerification: undefined,
|
|
103
|
+
};
|
|
104
|
+
const goals =
|
|
105
|
+
input.receiptKind === "final-aggregate"
|
|
106
|
+
? input.plan.goals.map(goal => ({
|
|
107
|
+
...goal,
|
|
108
|
+
status: goal.id === input.goal.id ? input.beforeStatus : goal.status,
|
|
109
|
+
updatedAt: goal.id === input.goal.id ? input.targetGoalUpdatedAt : goal.updatedAt,
|
|
110
|
+
evidence: goal.id === input.goal.id ? undefined : goal.evidence,
|
|
111
|
+
completedAt: goal.id === input.goal.id ? undefined : goal.completedAt,
|
|
112
|
+
completionVerification: undefined,
|
|
113
|
+
}))
|
|
114
|
+
: [targetGoalSnapshot];
|
|
115
|
+
return {
|
|
116
|
+
version: input.plan.version,
|
|
117
|
+
brief: input.plan.brief,
|
|
118
|
+
skcGoalMode: input.plan.skcGoalMode,
|
|
119
|
+
skcObjective: input.plan.skcObjective,
|
|
120
|
+
skcObjectiveAliases: input.plan.skcObjectiveAliases,
|
|
121
|
+
createdAt: input.plan.createdAt,
|
|
122
|
+
goals,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function computeUltragoalPlanGeneration(input: {
|
|
127
|
+
plan: UltragoalPlan;
|
|
128
|
+
ledger: readonly UltragoalLedgerEvent[];
|
|
129
|
+
goal: UltragoalGoal;
|
|
130
|
+
receiptKind: UltragoalReceiptKind;
|
|
131
|
+
beforeStatus: UltragoalGoalStatus;
|
|
132
|
+
excludeEventId?: string;
|
|
133
|
+
targetGoalUpdatedAt?: string;
|
|
134
|
+
}): {
|
|
135
|
+
planGeneration: string;
|
|
136
|
+
basis: UltragoalCompletionVerification["basis"];
|
|
137
|
+
} {
|
|
138
|
+
const relevantGoals = receiptRelevantGoals(input.plan, input.goal, input.receiptKind);
|
|
139
|
+
const relevantGoalIds = relevantGoals.map(goal => goal.id);
|
|
140
|
+
const targetGoalUpdatedAt = input.targetGoalUpdatedAt ?? input.goal.updatedAt;
|
|
141
|
+
const planHashBeforeCheckpoint = hashStructuredValue(
|
|
142
|
+
planSnapshotForReceipt({
|
|
143
|
+
plan: input.plan,
|
|
144
|
+
goal: input.goal,
|
|
145
|
+
beforeStatus: input.beforeStatus,
|
|
146
|
+
targetGoalUpdatedAt,
|
|
147
|
+
receiptKind: input.receiptKind,
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
const requiredGoalSetHashBeforeCheckpoint = hashStructuredValue(
|
|
151
|
+
relevantGoals.map(goal => ({
|
|
152
|
+
id: goal.id,
|
|
153
|
+
status: goal.id === input.goal.id ? input.beforeStatus : goal.status,
|
|
154
|
+
updatedAt: goal.id === input.goal.id ? targetGoalUpdatedAt : goal.updatedAt,
|
|
155
|
+
})),
|
|
156
|
+
);
|
|
157
|
+
const basis: UltragoalCompletionVerification["basis"] = {
|
|
158
|
+
planHashBeforeCheckpoint,
|
|
159
|
+
latestRelevantLedgerEventIdBeforeCheckpoint: latestRelevantLedgerEventId(
|
|
160
|
+
input.ledger,
|
|
161
|
+
relevantGoalIds,
|
|
162
|
+
input.excludeEventId,
|
|
163
|
+
),
|
|
164
|
+
goalUpdatedAtBeforeCheckpoint: targetGoalUpdatedAt,
|
|
165
|
+
relevantGoalIdsBeforeCheckpoint: relevantGoalIds,
|
|
166
|
+
requiredGoalSetHashBeforeCheckpoint,
|
|
167
|
+
};
|
|
168
|
+
return { planGeneration: hashStructuredValue(basis), basis };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function findLedgerReceiptEvent(
|
|
172
|
+
ledger: readonly UltragoalLedgerEvent[],
|
|
173
|
+
receipt: UltragoalCompletionVerification,
|
|
174
|
+
): UltragoalLedgerEvent | null {
|
|
175
|
+
return (
|
|
176
|
+
ledger.find(event => {
|
|
177
|
+
if (event.eventId !== receipt.checkpointLedgerEventId) return false;
|
|
178
|
+
if (event.event !== "goal_checkpointed") return false;
|
|
179
|
+
if (event.goalId !== receipt.goalId) return false;
|
|
180
|
+
const eventReceipt = event.completionVerification as UltragoalCompletionVerification | undefined;
|
|
181
|
+
return (
|
|
182
|
+
event.status === "complete" &&
|
|
183
|
+
eventReceipt?.receiptId === receipt.receiptId &&
|
|
184
|
+
eventReceipt.receiptKind === receipt.receiptKind &&
|
|
185
|
+
eventReceipt.planGeneration === receipt.planGeneration
|
|
186
|
+
);
|
|
187
|
+
}) ?? null
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function validateReceiptFreshBase(input: {
|
|
192
|
+
plan: UltragoalPlan;
|
|
193
|
+
ledger: readonly UltragoalLedgerEvent[];
|
|
194
|
+
goal: UltragoalGoal;
|
|
195
|
+
receipt: UltragoalCompletionVerification;
|
|
196
|
+
receiptKind: UltragoalReceiptKind;
|
|
197
|
+
}): UltragoalReceiptFreshnessDiagnostic | null {
|
|
198
|
+
if (
|
|
199
|
+
input.receipt.schemaVersion !== 1 ||
|
|
200
|
+
input.receipt.goalId !== input.goal.id ||
|
|
201
|
+
input.receipt.receiptKind !== input.receiptKind ||
|
|
202
|
+
!input.receipt.planGeneration ||
|
|
203
|
+
!input.receipt.checkpointLedgerEventId
|
|
204
|
+
) {
|
|
205
|
+
return {
|
|
206
|
+
state: "active_stale_receipt",
|
|
207
|
+
message: `Ultragoal ${input.goal.id} receipt is malformed or stale.`,
|
|
208
|
+
goalId: input.goal.id,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const event = findLedgerReceiptEvent(input.ledger, input.receipt);
|
|
212
|
+
if (!event)
|
|
213
|
+
return {
|
|
214
|
+
state: "active_stale_receipt",
|
|
215
|
+
message: `Ultragoal ${input.goal.id} receipt ledger event is missing.`,
|
|
216
|
+
goalId: input.goal.id,
|
|
217
|
+
};
|
|
218
|
+
const generation = computeUltragoalPlanGeneration({
|
|
219
|
+
plan: input.plan,
|
|
220
|
+
ledger: input.ledger,
|
|
221
|
+
goal: input.goal,
|
|
222
|
+
receiptKind: input.receiptKind,
|
|
223
|
+
beforeStatus: input.receipt.goalStatusBeforeCheckpoint,
|
|
224
|
+
excludeEventId: input.receipt.checkpointLedgerEventId,
|
|
225
|
+
});
|
|
226
|
+
if (generation.planGeneration !== input.receipt.planGeneration)
|
|
227
|
+
return {
|
|
228
|
+
state: "active_stale_receipt",
|
|
229
|
+
message: `Ultragoal ${input.goal.id} receipt generation is stale.`,
|
|
230
|
+
goalId: input.goal.id,
|
|
231
|
+
};
|
|
232
|
+
if (hashStructuredValue(event.qualityGateJson) !== input.receipt.qualityGateHash)
|
|
233
|
+
return {
|
|
234
|
+
state: "active_dirty_quality_gate",
|
|
235
|
+
message: `Ultragoal ${input.goal.id} receipt quality-gate hash does not match ledger.`,
|
|
236
|
+
goalId: input.goal.id,
|
|
237
|
+
};
|
|
238
|
+
if (input.goal.updatedAt !== input.receipt.verifiedAt)
|
|
239
|
+
return {
|
|
240
|
+
state: "active_stale_receipt",
|
|
241
|
+
message: `Ultragoal ${input.goal.id} receipt target changed after verification.`,
|
|
242
|
+
goalId: input.goal.id,
|
|
243
|
+
};
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function findFreshBatchCloseReceipt(input: {
|
|
248
|
+
plan: UltragoalPlan;
|
|
249
|
+
ledger: readonly UltragoalLedgerEvent[];
|
|
250
|
+
deferredGoal: UltragoalGoal;
|
|
251
|
+
deferredReceipt: UltragoalCompletionVerification;
|
|
252
|
+
}): UltragoalCompletionVerification | null {
|
|
253
|
+
const batch = input.deferredReceipt.validationBatch;
|
|
254
|
+
if (batch?.role !== "deferred-member") return null;
|
|
255
|
+
const finalGoal = input.plan.goals.find(goal => goal.id === batch.finalGoalId);
|
|
256
|
+
const finalReceipt = finalGoal?.completionVerification;
|
|
257
|
+
if (!finalGoal || finalReceipt?.validationBatch?.role !== "batch-close") return null;
|
|
258
|
+
if (
|
|
259
|
+
finalReceipt.validationBatch.batchId !== batch.batchId ||
|
|
260
|
+
finalReceipt.validationBatch.memberReceiptIds[input.deferredGoal.id] !== input.deferredReceipt.receiptId
|
|
261
|
+
)
|
|
262
|
+
return null;
|
|
263
|
+
const diagnostic = validateReceiptFreshBase({
|
|
264
|
+
plan: input.plan,
|
|
265
|
+
ledger: input.ledger,
|
|
266
|
+
goal: finalGoal,
|
|
267
|
+
receipt: finalReceipt,
|
|
268
|
+
receiptKind: finalReceipt.receiptKind,
|
|
269
|
+
});
|
|
270
|
+
return diagnostic ? null : finalReceipt;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function validateDeferredMemberReceiptFresh(input: {
|
|
274
|
+
plan: UltragoalPlan;
|
|
275
|
+
ledger: readonly UltragoalLedgerEvent[];
|
|
276
|
+
goal: UltragoalGoal;
|
|
277
|
+
receipt: UltragoalCompletionVerification;
|
|
278
|
+
receiptKind: UltragoalReceiptKind;
|
|
279
|
+
requireClose: boolean;
|
|
280
|
+
}): UltragoalReceiptFreshnessDiagnostic {
|
|
281
|
+
const batch = input.receipt.validationBatch;
|
|
282
|
+
if (
|
|
283
|
+
batch?.role !== "deferred-member" ||
|
|
284
|
+
input.goal.validationBatch?.metadataHash !== batch.metadataHash ||
|
|
285
|
+
input.goal.validationBatch.batchId !== batch.batchId
|
|
286
|
+
) {
|
|
287
|
+
return {
|
|
288
|
+
state: "active_stale_receipt",
|
|
289
|
+
message: `Ultragoal ${input.goal.id} deferred receipt is malformed or stale.`,
|
|
290
|
+
goalId: input.goal.id,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const base = validateReceiptFreshBase(input);
|
|
294
|
+
if (base) return base;
|
|
295
|
+
if (
|
|
296
|
+
input.requireClose &&
|
|
297
|
+
!findFreshBatchCloseReceipt({
|
|
298
|
+
plan: input.plan,
|
|
299
|
+
ledger: input.ledger,
|
|
300
|
+
deferredGoal: input.goal,
|
|
301
|
+
deferredReceipt: input.receipt,
|
|
302
|
+
})
|
|
303
|
+
) {
|
|
304
|
+
return {
|
|
305
|
+
state: "active_missing_final_receipt",
|
|
306
|
+
message: `Ultragoal ${input.goal.id} is deferred to validation batch ${batch.batchId} until final goal ${batch.finalGoalId} closes the batch`,
|
|
307
|
+
goalId: input.goal.id,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
state: "active_verified_complete",
|
|
312
|
+
message: `Ultragoal ${input.goal.id} has a fresh deferred validation batch receipt.`,
|
|
313
|
+
goalId: input.goal.id,
|
|
314
|
+
};
|
|
315
|
+
}
|