tickmarkr 2.6.1 → 2.6.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/README.md +14 -3
- package/dist/adapters/catalog-remote.js +89 -47
- package/dist/adapters/claude-code.js +9 -6
- package/dist/adapters/codex.js +7 -4
- package/dist/adapters/prompt.d.ts +1 -0
- package/dist/adapters/prompt.js +14 -6
- package/dist/adapters/registry.js +3 -3
- package/dist/adapters/types.d.ts +12 -4
- package/dist/adapters/types.js +6 -0
- package/dist/cli/commands/approve.d.ts +5 -1
- package/dist/cli/commands/approve.js +66 -23
- package/dist/cli/commands/compile.js +13 -3
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.js +11 -3
- package/dist/cli/commands/fleet.js +45 -7
- package/dist/cli/commands/report.js +18 -2
- package/dist/cli/commands/resume.js +4 -2
- package/dist/cli/commands/status.js +24 -19
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.js +9 -2
- package/dist/config/config.d.ts +20 -0
- package/dist/config/config.js +47 -8
- package/dist/config/fleet-overlay.d.ts +1 -0
- package/dist/config/fleet-overlay.js +56 -0
- package/dist/drivers/orca.d.ts +26 -1
- package/dist/drivers/orca.js +193 -60
- package/dist/eval/canary.d.ts +2 -1
- package/dist/eval/canary.js +2 -2
- package/dist/eval/dispatch.js +1 -0
- package/dist/gates/acceptance.d.ts +2 -1
- package/dist/gates/acceptance.js +7 -2
- package/dist/gates/baseline.d.ts +12 -1
- package/dist/gates/baseline.js +11 -4
- package/dist/gates/llm.d.ts +5 -4
- package/dist/gates/llm.js +13 -13
- package/dist/gates/review.d.ts +8 -0
- package/dist/gates/review.js +40 -4
- package/dist/gates/run-gates.d.ts +2 -1
- package/dist/gates/run-gates.js +27 -13
- package/dist/gates/test-manifest.d.ts +3 -1
- package/dist/gates/test-manifest.js +9 -2
- package/dist/graph/schema.d.ts +2 -0
- package/dist/graph/schema.js +2 -0
- package/dist/plan/scope.js +2 -2
- package/dist/route/preference.d.ts +20 -2
- package/dist/route/preference.js +48 -13
- package/dist/route/router.js +30 -15
- package/dist/run/consult.d.ts +13 -1
- package/dist/run/consult.js +14 -5
- package/dist/run/daemon.d.ts +37 -2
- package/dist/run/daemon.js +579 -141
- package/dist/run/git.d.ts +8 -0
- package/dist/run/git.js +14 -0
- package/dist/run/journal.d.ts +126 -3
- package/dist/run/journal.js +410 -37
- package/dist/run/merge.d.ts +3 -1
- package/dist/run/merge.js +3 -2
- package/dist/run/operator-summary.d.ts +3 -0
- package/dist/run/operator-summary.js +3 -1
- package/dist/run/protocol.d.ts +31 -1
- package/dist/run/protocol.js +3 -1
- package/dist/run/supervision.d.ts +7 -1
- package/dist/run/supervision.js +5 -2
- package/dist/tui/cockpit/board.js +3 -3
- package/dist/tui/cockpit/decision-actions.d.ts +8 -5
- package/dist/tui/cockpit/decision-actions.js +55 -32
- package/dist/tui/cockpit/derive.js +13 -2
- package/dist/tui/cockpit/live-runtime.d.ts +10 -0
- package/dist/tui/cockpit/live-runtime.js +50 -3
- package/dist/tui/cockpit/run-cockpit.d.ts +3 -0
- package/dist/tui/cockpit/run-cockpit.js +26 -1
- package/dist/tui/cockpit/run-view.d.ts +9 -3
- package/dist/tui/cockpit/run-view.js +60 -7
- package/dist/tui/cockpit/setup-cockpit.d.ts +4 -0
- package/dist/tui/cockpit/setup-cockpit.js +6 -3
- package/dist/tui/ink/fleet-app.d.ts +15 -3
- package/dist/tui/ink/fleet-app.js +91 -22
- package/package.json +2 -1
- package/schema/config.schema.json +818 -0
- package/skills/tickmarkr-loop/SKILL.md +8 -2
- package/skills/tickmarkr-overseer/SKILL.md +42 -0
- package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +88 -0
- package/skills/tickmarkr-overseer/scripts/context-statusline.sh +81 -0
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +36 -34
- package/skills/tickmarkr-overseer/scripts/watch-journal.sh +6 -4
package/dist/run/merge.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TickmarkrConfig } from "../config/config.js";
|
|
2
|
-
import { type Baseline, type GateEvidenceOptions, type FailureClassification } from "../gates/baseline.js";
|
|
2
|
+
import { type Baseline, type BaselineProvenance, type GateEvidenceOptions, type FailureClassification } from "../gates/baseline.js";
|
|
3
3
|
import type { GateEvidenceReceipt } from "./protocol.js";
|
|
4
4
|
export interface TipVerifyResult {
|
|
5
5
|
evidenceReceipt?: GateEvidenceReceipt;
|
|
@@ -21,6 +21,8 @@ export interface TipVerifyResult {
|
|
|
21
21
|
spawnedCommand?: string;
|
|
22
22
|
/** Q121s: nonzero exit whose failures are ALL baseline-recorded — forgiven exactly as the battery forgives. */
|
|
23
23
|
forgiven?: boolean;
|
|
24
|
+
/** OBS-1123: the capture that recorded the forgiven fingerprints; absent for a legacy baseline that never said. */
|
|
25
|
+
baselineProvenance?: BaselineProvenance;
|
|
24
26
|
/** D-131: carried from a persisted per-gate verdict — this cycle did NOT execute the command. */
|
|
25
27
|
reused?: true;
|
|
26
28
|
/**
|
package/dist/run/merge.js
CHANGED
|
@@ -225,6 +225,7 @@ export async function verifyIntegrationTip(intWt, commands, runDir, baseline, ev
|
|
|
225
225
|
exitCode: hit.exitCode ?? 0,
|
|
226
226
|
fingerprints: hit.meta?.fingerprints ?? [],
|
|
227
227
|
...(hit.meta?.forgiven ? { forgiven: true } : {}),
|
|
228
|
+
...(hit.meta?.forgiven && hit.meta.baselineProvenance ? { baselineProvenance: hit.meta.baselineProvenance } : {}),
|
|
228
229
|
details: formatReusedDetails(hit.details, id),
|
|
229
230
|
...(hit.meta?.reportPath ? { reportPath: hit.meta.reportPath } : {}),
|
|
230
231
|
...(hit.meta?.spawnedCommand ? { spawnedCommand: hit.meta.spawnedCommand } : {}),
|
|
@@ -382,7 +383,7 @@ export async function verifyIntegrationTip(intWt, commands, runDir, baseline, ev
|
|
|
382
383
|
+ `${describeCapacity(entry?.capacity)} and this verification ran under ${describeCapacity(r.capacity)} `
|
|
383
384
|
+ `— forgiveness across a changed capacity is not evidence`
|
|
384
385
|
: `exit ${r.code}`)),
|
|
385
|
-
...(forgiven && !deficit ? { forgiven: true } : {}),
|
|
386
|
+
...(forgiven && !deficit ? { forgiven: true, ...(baseline?.provenance ? { baselineProvenance: baseline.provenance } : {}) } : {}),
|
|
386
387
|
...(cause ? { cause } : {}),
|
|
387
388
|
...(pass ? {} : { artifact }),
|
|
388
389
|
};
|
|
@@ -401,7 +402,7 @@ export async function verifyIntegrationTip(intWt, commands, runDir, baseline, ev
|
|
|
401
402
|
const id = pending.get(result.gate);
|
|
402
403
|
if (id && id.tree === completedTree && !isInfraResult(result)) {
|
|
403
404
|
store.set(id, { ...result, meta: {
|
|
404
|
-
fingerprints: result.fingerprints, forgiven: result.forgiven,
|
|
405
|
+
fingerprints: result.fingerprints, forgiven: result.forgiven, baselineProvenance: result.baselineProvenance,
|
|
405
406
|
reportPath: result.reportPath, spawnedCommand: result.spawnedCommand,
|
|
406
407
|
artifact: result.artifact,
|
|
407
408
|
evidenceReceipt: result.evidenceReceipt, evidenceReceipts: result.evidenceReceipts,
|
|
@@ -13,6 +13,9 @@ export interface OperatorDecisionSnapshot {
|
|
|
13
13
|
readonly kind: string | undefined;
|
|
14
14
|
readonly tombstone: boolean;
|
|
15
15
|
readonly reason?: string;
|
|
16
|
+
/** OBS-1178: the park row's physical line and timestamp — the token a decision binds to. */
|
|
17
|
+
readonly line?: number;
|
|
18
|
+
readonly ts?: string;
|
|
16
19
|
};
|
|
17
20
|
readonly verbs: readonly string[];
|
|
18
21
|
readonly diagnostic?: string;
|
|
@@ -25,7 +25,9 @@ export function projectOperatorSummary(tasks, decisions) {
|
|
|
25
25
|
decisionRequired: verbs.length > 0,
|
|
26
26
|
...(diagnostic !== undefined || park.reason !== undefined
|
|
27
27
|
? { diagnostic: diagnostic ?? park.reason } : {}),
|
|
28
|
-
nextAction: verbs.length > 0
|
|
28
|
+
nextAction: verbs.length > 0
|
|
29
|
+
? `Choose a decision: ${verbs.join(", ")}${park.line !== undefined && park.ts !== undefined ? ` (--park ${park.line}@${park.ts})` : ""}`
|
|
30
|
+
: null,
|
|
29
31
|
};
|
|
30
32
|
if (!park.tombstone && park.kind === "infra" && task.missingCapability) {
|
|
31
33
|
const { capability, diagnostic: capabilityDiagnostic } = task.missingCapability;
|
package/dist/run/protocol.d.ts
CHANGED
|
@@ -66,10 +66,10 @@ export declare const GateEvidenceReceiptSchema: z.ZodObject<{
|
|
|
66
66
|
termination: z.ZodObject<{
|
|
67
67
|
kind: z.ZodEnum<{
|
|
68
68
|
unknown: "unknown";
|
|
69
|
+
timeout: "timeout";
|
|
69
70
|
signal: "signal";
|
|
70
71
|
"not-started": "not-started";
|
|
71
72
|
exit: "exit";
|
|
72
|
-
timeout: "timeout";
|
|
73
73
|
}>;
|
|
74
74
|
exitCode: z.ZodNullable<z.ZodNumber>;
|
|
75
75
|
signal: z.ZodNullable<z.ZodString>;
|
|
@@ -222,6 +222,11 @@ export declare const RoleInvocationStartEventSchema: z.ZodObject<{
|
|
|
222
222
|
adapter: z.ZodOptional<z.ZodString>;
|
|
223
223
|
model: z.ZodOptional<z.ZodString>;
|
|
224
224
|
vendor: z.ZodOptional<z.ZodString>;
|
|
225
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
226
|
+
low: "low";
|
|
227
|
+
medium: "medium";
|
|
228
|
+
high: "high";
|
|
229
|
+
}>>;
|
|
225
230
|
evidence: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
226
231
|
}, z.core.$strict>;
|
|
227
232
|
}, z.core.$strict>;
|
|
@@ -240,6 +245,11 @@ export declare const RoleInvocationTerminalEventSchema: z.ZodObject<{
|
|
|
240
245
|
adapter: z.ZodOptional<z.ZodString>;
|
|
241
246
|
model: z.ZodOptional<z.ZodString>;
|
|
242
247
|
vendor: z.ZodOptional<z.ZodString>;
|
|
248
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
249
|
+
low: "low";
|
|
250
|
+
medium: "medium";
|
|
251
|
+
high: "high";
|
|
252
|
+
}>>;
|
|
243
253
|
evidence: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
244
254
|
outcome: z.ZodEnum<{
|
|
245
255
|
failed: "failed";
|
|
@@ -352,6 +362,11 @@ export declare const DecisionEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
352
362
|
adapter: z.ZodOptional<z.ZodString>;
|
|
353
363
|
model: z.ZodOptional<z.ZodString>;
|
|
354
364
|
vendor: z.ZodOptional<z.ZodString>;
|
|
365
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
366
|
+
low: "low";
|
|
367
|
+
medium: "medium";
|
|
368
|
+
high: "high";
|
|
369
|
+
}>>;
|
|
355
370
|
evidence: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
356
371
|
}, z.core.$strict>;
|
|
357
372
|
}, z.core.$strict>, z.ZodObject<{
|
|
@@ -369,6 +384,11 @@ export declare const DecisionEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
369
384
|
adapter: z.ZodOptional<z.ZodString>;
|
|
370
385
|
model: z.ZodOptional<z.ZodString>;
|
|
371
386
|
vendor: z.ZodOptional<z.ZodString>;
|
|
387
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
388
|
+
low: "low";
|
|
389
|
+
medium: "medium";
|
|
390
|
+
high: "high";
|
|
391
|
+
}>>;
|
|
372
392
|
evidence: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
373
393
|
outcome: z.ZodEnum<{
|
|
374
394
|
failed: "failed";
|
|
@@ -473,6 +493,11 @@ export declare const DecisionEventWriteSchema: z.ZodDiscriminatedUnion<[z.ZodObj
|
|
|
473
493
|
adapter: z.ZodOptional<z.ZodString>;
|
|
474
494
|
model: z.ZodOptional<z.ZodString>;
|
|
475
495
|
vendor: z.ZodOptional<z.ZodString>;
|
|
496
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
497
|
+
low: "low";
|
|
498
|
+
medium: "medium";
|
|
499
|
+
high: "high";
|
|
500
|
+
}>>;
|
|
476
501
|
evidence: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
477
502
|
}, z.core.$strict>;
|
|
478
503
|
event: z.ZodLiteral<"role-invocation-start">;
|
|
@@ -489,6 +514,11 @@ export declare const DecisionEventWriteSchema: z.ZodDiscriminatedUnion<[z.ZodObj
|
|
|
489
514
|
adapter: z.ZodOptional<z.ZodString>;
|
|
490
515
|
model: z.ZodOptional<z.ZodString>;
|
|
491
516
|
vendor: z.ZodOptional<z.ZodString>;
|
|
517
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
518
|
+
low: "low";
|
|
519
|
+
medium: "medium";
|
|
520
|
+
high: "high";
|
|
521
|
+
}>>;
|
|
492
522
|
evidence: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
493
523
|
outcome: z.ZodEnum<{
|
|
494
524
|
failed: "failed";
|
package/dist/run/protocol.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { GATE_NAMES } from "../graph/schema.js";
|
|
2
|
+
import { EFFORTS, GATE_NAMES } from "../graph/schema.js";
|
|
3
3
|
import { normalizeGateOutcome } from "./outcome.js";
|
|
4
4
|
// T32 (F2): the decision protocol is deliberately smaller than the journal. Existing journals contain
|
|
5
5
|
// many observational event types and must remain readable; only the five event names below assert a
|
|
@@ -133,6 +133,8 @@ const RoleInvocationIdentityDataSchema = z.object({
|
|
|
133
133
|
adapter: NonEmptyStringSchema.optional(),
|
|
134
134
|
model: NonEmptyStringSchema.optional(),
|
|
135
135
|
vendor: NonEmptyStringSchema.optional(),
|
|
136
|
+
// OBS-1182: the effort the seat actually launched at; absent = the CLI's own default, never a level.
|
|
137
|
+
effort: z.enum(EFFORTS).optional(),
|
|
136
138
|
evidence: EvidenceSchema.optional(),
|
|
137
139
|
});
|
|
138
140
|
const RoleInvocationStartDataSchema = RoleInvocationIdentityDataSchema.strict();
|
|
@@ -48,6 +48,7 @@ export interface SupervisionArm {
|
|
|
48
48
|
export declare const supervisionArmPath: (repoRoot: string, tier: SupervisionTier) => string;
|
|
49
49
|
/** Explicit arming acknowledges the current marker without deleting it. */
|
|
50
50
|
export declare function newSupervisionArm(repoRoot: string, tier: SupervisionTier, armId?: string, initial?: boolean): SupervisionArm;
|
|
51
|
+
export declare const supervisionPresencePath: (repoRoot: string, tier: SupervisionTier, id: string) => string;
|
|
51
52
|
export declare function beatSupervision(repoRoot: string, tier: SupervisionTier, seat?: string, observation?: SupervisionBeatObservation, options?: {
|
|
52
53
|
arm?: SupervisionArm;
|
|
53
54
|
armId?: string;
|
|
@@ -69,7 +70,10 @@ export declare function supervisionStatus(repoRoot: string, tier: SupervisionTie
|
|
|
69
70
|
export declare const readSupervision: (repoRoot: string, now?: number) => TierLiveness[];
|
|
70
71
|
/** One line, one word per tier. Shared by both status surfaces so neither can render a state twice. */
|
|
71
72
|
export declare const supervisionText: (tiers: readonly TierLiveness[], divider?: string) => string;
|
|
72
|
-
/** A board's durable identity is independent of its short title and of other observers.
|
|
73
|
+
/** A board's durable identity is independent of its short title and of other observers.
|
|
74
|
+
* `handle` is the terminal the claiming observer reports running in, when its host exports one: the
|
|
75
|
+
* one fact that names the board's pane when the placing split's receipt never arrived (OBS-1172).
|
|
76
|
+
* `retired` marks a tombstone a driver keeps in the record's place; it is never answered again. */
|
|
73
77
|
export interface WatchBoardOwner {
|
|
74
78
|
repo: string;
|
|
75
79
|
runId: string;
|
|
@@ -80,6 +84,8 @@ export interface WatchBoardOwner {
|
|
|
80
84
|
token: string;
|
|
81
85
|
pid?: number;
|
|
82
86
|
armId?: string;
|
|
87
|
+
handle?: string;
|
|
88
|
+
retired?: true;
|
|
83
89
|
}
|
|
84
90
|
export declare const WATCH_OWNER_ENV = "TICKMARKR_WATCH_OWNER";
|
|
85
91
|
export declare function readWatchBoard(repo: string, runId: string): WatchBoardOwner | undefined;
|
package/dist/run/supervision.js
CHANGED
|
@@ -154,7 +154,7 @@ const supervisionObligationPath = (repoRoot, tier) => join(supervisionDir(repoRo
|
|
|
154
154
|
// Freshness decides presence, never a process table (SUP-02): a watcher that is killed cannot remove
|
|
155
155
|
// its own file, and an unremoved file ages past the same ceiling a beat does and stops counting.
|
|
156
156
|
const presencePrefix = (tier) => `${tier}.live.`;
|
|
157
|
-
const supervisionPresencePath = (repoRoot, tier, id) => join(supervisionDir(repoRoot), `${presencePrefix(tier)}${id}`);
|
|
157
|
+
export const supervisionPresencePath = (repoRoot, tier, id) => join(supervisionDir(repoRoot), `${presencePrefix(tier)}${id}`);
|
|
158
158
|
/** Every presence file on this tier, by name. Missing directory ⇒ nobody is present. */
|
|
159
159
|
const presenceNames = (repoRoot, tier) => {
|
|
160
160
|
try {
|
|
@@ -567,6 +567,8 @@ export const supervisionText = (tiers, divider = " · ") => `supervision: ${tier
|
|
|
567
567
|
`${t.clearOwedSince ? ` CLEAR-OWED since ${t.clearOwedSince}` : ""}` +
|
|
568
568
|
`${t.clearOwedUnreadable ? " CLEAR-OWED unreadable" : ""}`).join(divider)}`;
|
|
569
569
|
export const WATCH_OWNER_ENV = "TICKMARKR_WATCH_OWNER";
|
|
570
|
+
/** Orca exports each terminal's own handle into the processes it runs. */
|
|
571
|
+
const TERMINAL_HANDLE_ENV = "ORCA_TERMINAL_HANDLE";
|
|
570
572
|
const boardPath = (repo, runId) => join(supervisionDir(repo), `watch-board.${parseRunId(runId)}.json`);
|
|
571
573
|
const boardMessagePath = (owner, kind) => {
|
|
572
574
|
if (!/^[a-f0-9-]{36}$/.test(owner.token))
|
|
@@ -642,7 +644,8 @@ export function observeNamedRun(repo, runId, env = process.env) {
|
|
|
642
644
|
const candidate = readWatchBoard(repo, runId);
|
|
643
645
|
if (!candidate || candidate.token !== token || candidate.pid !== undefined)
|
|
644
646
|
throw new Error("watch ownership unknown; refusing narrator acknowledgement");
|
|
645
|
-
|
|
647
|
+
const handle = env[TERMINAL_HANDLE_ENV]?.trim();
|
|
648
|
+
owner = { ...candidate, pid: process.pid, armId: armed.id, ...(handle ? { handle } : {}) };
|
|
646
649
|
atomicRecord(boardPath(repo, runId), owner);
|
|
647
650
|
}
|
|
648
651
|
}
|
|
@@ -47,9 +47,9 @@ const AREAS = [
|
|
|
47
47
|
["FLEET", [/^src\/(route|config|adapters)\//]],
|
|
48
48
|
["RUN", [/^src\/(run|drivers|eval)\//]],
|
|
49
49
|
["GATE", [/^src\/gates\//]],
|
|
50
|
-
["UI", [/^src\/(cli|tui|report)\//, /^src\/brand\.ts
|
|
51
|
-
["DOCS", [/^(docs|skills)\//]],
|
|
52
|
-
["REPO", [/^(scripts|schema)\//, /^\.github\//, /^tests\/(lint|repo|helpers)\//, /^(vitest\.config|package\.json|tsconfig)/]],
|
|
50
|
+
["UI", [/^src\/(cli|tui|report)\//, /^src\/brand\.ts/, /^tests\/cockpit\//]],
|
|
51
|
+
["DOCS", [/^(docs|skills)\//, /^\.claude\/skills\//]],
|
|
52
|
+
["REPO", [/^(scripts|schema)\//, /^\.github\//, /^tests\/(lint|repo|helpers|fixtures)\//, /^(vitest\.config|package\.json|tsconfig)/]],
|
|
53
53
|
];
|
|
54
54
|
export const BOARD_AREA_ORDER = AREAS.map(([n]) => n);
|
|
55
55
|
const expandBraces = (p) => {
|
|
@@ -25,6 +25,10 @@ export interface DecisionCommand {
|
|
|
25
25
|
readonly reason?: string;
|
|
26
26
|
/** `--review-rounds`: the review-round ceiling the release carries. */
|
|
27
27
|
readonly reviewRounds?: number;
|
|
28
|
+
/** OBS-1178: `--park <line>@<ts>` — the displayed park this decision binds to. */
|
|
29
|
+
readonly park?: string;
|
|
30
|
+
/** OBS-1178: `--gate <gate>` — the failed gate a waive binds to. */
|
|
31
|
+
readonly gate?: string;
|
|
28
32
|
}
|
|
29
33
|
/** The exact argv the production command receives — built only from the closed verb set. */
|
|
30
34
|
export declare function decisionArgv(command: DecisionCommand, { runId, by }: {
|
|
@@ -57,13 +61,11 @@ export declare const journalFileFor: (cwd: string, runId: string) => string;
|
|
|
57
61
|
* the named verb is outside the park's table, so a confirm inset can never promise a decision the
|
|
58
62
|
* command would refuse. The enactor is read from the run lock now, and read again at the receipt.
|
|
59
63
|
*/
|
|
60
|
-
export declare function previewDecision(
|
|
64
|
+
export declare function previewDecision(named: DecisionCommand, { cwd, runId, by }: {
|
|
61
65
|
cwd: string;
|
|
62
66
|
runId: string;
|
|
63
67
|
by: string;
|
|
64
68
|
}): DecisionPreviewResult;
|
|
65
|
-
/** Who enacts it — the three lock states, each stated by the command's own sentence. */
|
|
66
|
-
export declare function enactmentLines(run: ApprovalRunOwner, disposition: ApprovalDisposition): string[];
|
|
67
69
|
/** The confirm inset: run/task, the newest park line, actor/reason, exact argv, consequence, enactor. */
|
|
68
70
|
export declare function decisionConfirmLines(preview: DecisionPreview): readonly string[];
|
|
69
71
|
/** The task-approved row the file gained, located by its physical line. */
|
|
@@ -135,8 +137,7 @@ export interface DecisionKeyResult {
|
|
|
135
137
|
/**
|
|
136
138
|
* The reducer never writes and never reads a file; it names commands. `a` opens the menu for the
|
|
137
139
|
* selected park (a diagnostic park opens a menu with no verbs, stating why); ↑↓ move; Enter picks a
|
|
138
|
-
* verb
|
|
139
|
-
* cancel the deepest layer. Enter on a confirm is inert by construction.
|
|
140
|
+
* verb opens the confirm inset; `y` names the write; `n`/Esc cancel the deepest layer. Enter on a confirm is inert.
|
|
140
141
|
*/
|
|
141
142
|
export declare function applyDecisionKey(session: DecisionSession, event: DecisionKeyEvent, selected: RunDecision | undefined): DecisionKeyResult;
|
|
142
143
|
/** Store the preview a picked verb produced — or its refusal as the notice. */
|
|
@@ -145,3 +146,5 @@ export declare function withDecisionPreview(session: DecisionSession, result: De
|
|
|
145
146
|
export declare function withDecisionReceipt(session: DecisionSession, receipt: DecisionReceipt): DecisionSession;
|
|
146
147
|
/** The verbs the keybar may advertise for the current layer — nothing the flow cannot honour. */
|
|
147
148
|
export declare function decisionKeybar(session: DecisionSession, selected: RunDecision | undefined): string;
|
|
149
|
+
/** Who enacts it — the three lock states, each stated by the command's own sentence. */
|
|
150
|
+
export declare function enactmentLines(run: ApprovalRunOwner, disposition: ApprovalDisposition): string[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { APPROVAL_ENACTS, approvalDispositionForRelease, approvalEnactment, approvalRunOwner, approve, DECISION_VERBS, newestPark, permittedDecisionVerbs, readJournalEvents, releaseForDecision, } from "../../cli/commands/approve.js";
|
|
2
|
+
import { APPROVAL_ENACTS, approvalDispositionForRelease, approvalEnactment, approvalRunOwner, approve, DECISION_VERBS, newestPark, parkToken, permittedDecisionVerbs, readJournalEvents, releaseForDecision, } from "../../cli/commands/approve.js";
|
|
3
3
|
import { stateDirName } from "../../graph/graph.js";
|
|
4
|
-
import { Journal } from "../../run/journal.js";
|
|
4
|
+
import { bindingToken, effectiveDecisions, Journal, physicalLine, recordedBinding } from "../../run/journal.js";
|
|
5
5
|
const parkDiagnostic = (park) => {
|
|
6
6
|
if (park.kind === "scope-request")
|
|
7
7
|
return park.approveCommand ?? "scope-request requires tickmarkr approve with --files <glob,…>";
|
|
@@ -50,6 +50,8 @@ export function decisionArgv(command, { runId, by }) {
|
|
|
50
50
|
const flag = { approve: [], waive: ["--waive"], uphold: ["--uphold"], recheck: ["--recheck"] }[command.verb];
|
|
51
51
|
return [
|
|
52
52
|
runId, command.taskId, ...flag,
|
|
53
|
+
...(command.park === undefined ? [] : ["--park", command.park]),
|
|
54
|
+
...(command.gate === undefined ? [] : ["--gate", command.gate]),
|
|
53
55
|
...(command.reviewRounds === undefined ? [] : ["--review-rounds", String(command.reviewRounds)]),
|
|
54
56
|
"--by", by,
|
|
55
57
|
...(command.reason === undefined ? [] : ["--reason", command.reason]),
|
|
@@ -62,10 +64,9 @@ export const journalFileFor = (cwd, runId) => join(stateDirName(cwd), "runs", ru
|
|
|
62
64
|
* the named verb is outside the park's table, so a confirm inset can never promise a decision the
|
|
63
65
|
* command would refuse. The enactor is read from the run lock now, and read again at the receipt.
|
|
64
66
|
*/
|
|
65
|
-
export function previewDecision(
|
|
66
|
-
let argv;
|
|
67
|
+
export function previewDecision(named, { cwd, runId, by }) {
|
|
67
68
|
try {
|
|
68
|
-
|
|
69
|
+
decisionArgv(named, { runId, by });
|
|
69
70
|
}
|
|
70
71
|
catch (e) {
|
|
71
72
|
return { ok: false, refusal: e.message };
|
|
@@ -78,15 +79,29 @@ export function previewDecision(command, { cwd, runId, by }) {
|
|
|
78
79
|
catch (e) {
|
|
79
80
|
return { ok: false, refusal: e.message };
|
|
80
81
|
}
|
|
81
|
-
const park = newestPark(events,
|
|
82
|
+
const park = newestPark(events, named.taskId, sourceIndexes);
|
|
82
83
|
if (!park)
|
|
83
|
-
return { ok: false, refusal: `task ${
|
|
84
|
+
return { ok: false, refusal: `task ${named.taskId} has no park in run ${runId} — nothing to decide` };
|
|
85
|
+
// OBS-1178: the argv binds the park this preview displays; approve refuses it once another opens.
|
|
86
|
+
const token = parkToken(park);
|
|
87
|
+
if (named.park !== undefined && named.park !== token)
|
|
88
|
+
return { ok: false, refusal: `task ${named.taskId}'s open park is ${token ?? "untimestamped"}, not ${named.park} — refresh and decide the open park` };
|
|
89
|
+
if (named.gate !== undefined && named.gate !== park.failedGate)
|
|
90
|
+
return { ok: false, refusal: `refusing mismatched gate for ${named.taskId}: --gate ${named.gate} but park ${token} failed ${park.failedGate ?? "no gate"} — nothing appended` };
|
|
91
|
+
const command = { ...named, ...(token === undefined ? {} : { park: token }), ...(named.verb === "waive" && named.gate === undefined && park.failedGate !== undefined ? { gate: park.failedGate } : {}) };
|
|
92
|
+
let argv;
|
|
93
|
+
try {
|
|
94
|
+
argv = decisionArgv(command, { runId, by });
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
return { ok: false, refusal: e.message };
|
|
98
|
+
}
|
|
84
99
|
const verbs = permittedDecisionVerbs(park);
|
|
85
100
|
if (!verbs.includes(command.verb)) {
|
|
86
101
|
const why = parkDiagnostic(park) ?? `permitted: ${verbs.join(", ")}`;
|
|
87
102
|
return { ok: false, refusal: `${command.verb} is not a decision for ${command.taskId}'s ${park.kind ?? "unknown"} park (${why})` };
|
|
88
103
|
}
|
|
89
|
-
const released = events
|
|
104
|
+
const released = releaseSince(events, park.index, command.taskId);
|
|
90
105
|
if (released) {
|
|
91
106
|
return { ok: false, refusal: `task ${command.taskId} was already released at #L${sourceIndexes[events.indexOf(released)] + 1} by ${String(released.data.by ?? "unknown")} — nothing to confirm` };
|
|
92
107
|
}
|
|
@@ -100,28 +115,12 @@ export function previewDecision(command, { cwd, runId, by }) {
|
|
|
100
115
|
};
|
|
101
116
|
}
|
|
102
117
|
const parkLine = (park) => `#L${park.line} ${park.kind ?? "unknown kind"}${park.failedGate ? ` · failed gate ${park.failedGate}` : ""}${park.reason ? ` · ${park.reason}` : ""}`;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (verb === "waive")
|
|
108
|
-
lines.push(` marks gate ${park.failedGate} satisfied; it does NOT mark the task done`);
|
|
109
|
-
if (verb === "recheck")
|
|
110
|
-
lines.push(" marks no gate satisfied; the whole declared battery re-runs");
|
|
111
|
-
if (verb === "uphold")
|
|
112
|
-
lines.push(" sides with the reviewer; funds one fixed attempt carrying the findings");
|
|
113
|
-
lines.push(` it will ${APPROVAL_ENACTS[disposition]}`);
|
|
114
|
-
return lines;
|
|
115
|
-
}
|
|
116
|
-
/** Who enacts it — the three lock states, each stated by the command's own sentence. */
|
|
117
|
-
export function enactmentLines(run, disposition) {
|
|
118
|
-
const owner = run.live
|
|
119
|
-
? "enactment matching live daemon — pending daemon enactment at its next task boundary"
|
|
120
|
-
: run.blockingRunId
|
|
121
|
-
? `enactment other live run \`${run.blockingRunId}\` holds the repository lock — recorded, not dispatched`
|
|
122
|
-
: `enactment no live owner — recorded, not dispatched; resume required: tickmarkr resume ${run.runId}`;
|
|
123
|
-
return [owner, ` ${approvalEnactment(disposition, run)}`];
|
|
118
|
+
/** The decision already open on this park, if any. OBS-1178: a refused or unsound row released nothing. */
|
|
119
|
+
function releaseSince(events, parkIndex, taskId) {
|
|
120
|
+
const effective = effectiveDecisions(events);
|
|
121
|
+
return events.find((e, i) => i > parkIndex && e.event === "task-approved" && e.taskId === taskId && effective.has(physicalLine(events, i)));
|
|
124
122
|
}
|
|
123
|
+
const quoteArg = (arg) => (/^[\w.:/@=-]+$/u.test(arg) ? arg : JSON.stringify(arg));
|
|
125
124
|
/** The confirm inset: run/task, the newest park line, actor/reason, exact argv, consequence, enactor. */
|
|
126
125
|
export function decisionConfirmLines(preview) {
|
|
127
126
|
return [
|
|
@@ -157,7 +156,7 @@ export async function executeDecision(preview, { cwd, command = approve }) {
|
|
|
157
156
|
if (!park || park.index !== preview.park.index || park.line !== preview.park.line || park.kind !== preview.park.kind) {
|
|
158
157
|
return { ok: false, preview, stale: true, refusal: `task ${named.taskId}'s park changed since preview (${park ? `now #L${park.line} ${park.kind ?? "unknown"}` : "no park now"}) — refresh and preview again` };
|
|
159
158
|
}
|
|
160
|
-
const released = before
|
|
159
|
+
const released = releaseSince(before, park.index, named.taskId);
|
|
161
160
|
if (released) {
|
|
162
161
|
return { ok: false, preview, stale: true, refusal: `task ${named.taskId} was released at #L${beforeSourceIndexes[before.indexOf(released)] + 1} by ${String(released.data.by ?? "unknown")} after this preview — no second decision appended; refresh` };
|
|
163
162
|
}
|
|
@@ -208,6 +207,7 @@ export async function executeDecision(preview, { cwd, command = approve }) {
|
|
|
208
207
|
via: "cli",
|
|
209
208
|
gate: named.verb === "waive" ? park.failedGate : named.verb === "uphold" ? "review" : undefined,
|
|
210
209
|
reviewRoundCeiling: named.reviewRounds,
|
|
210
|
+
park: parkToken(park),
|
|
211
211
|
};
|
|
212
212
|
const actual = {
|
|
213
213
|
release,
|
|
@@ -216,6 +216,7 @@ export async function executeDecision(preview, { cwd, command = approve }) {
|
|
|
216
216
|
via: appended.event.data.via,
|
|
217
217
|
gate: appended.event.data.gate,
|
|
218
218
|
reviewRoundCeiling: appended.event.data.reviewRoundCeiling,
|
|
219
|
+
park: recordedBinding(appended.event.data.park) === undefined ? undefined : bindingToken(recordedBinding(appended.event.data.park)),
|
|
219
220
|
};
|
|
220
221
|
const mismatch = Object.keys(expected)
|
|
221
222
|
.find((field) => actual[field] !== expected[field]);
|
|
@@ -264,8 +265,7 @@ export const initialDecisionSession = () => ({ menu: null, confirming: null, rec
|
|
|
264
265
|
/**
|
|
265
266
|
* The reducer never writes and never reads a file; it names commands. `a` opens the menu for the
|
|
266
267
|
* selected park (a diagnostic park opens a menu with no verbs, stating why); ↑↓ move; Enter picks a
|
|
267
|
-
* verb
|
|
268
|
-
* cancel the deepest layer. Enter on a confirm is inert by construction.
|
|
268
|
+
* verb opens the confirm inset; `y` names the write; `n`/Esc cancel the deepest layer. Enter on a confirm is inert.
|
|
269
269
|
*/
|
|
270
270
|
export function applyDecisionKey(session, event, selected) {
|
|
271
271
|
if (session.confirming !== null) {
|
|
@@ -315,3 +315,26 @@ export function decisionKeybar(session, selected) {
|
|
|
315
315
|
return session.menu.verbs.length === 0 ? "Esc Close" : "↑↓ Choose · Enter Preview · Esc Close";
|
|
316
316
|
return selected === undefined ? "" : "a Actions";
|
|
317
317
|
}
|
|
318
|
+
// Consequence and enactor lines, shared by the confirm inset and the receipt above. Kept below the key
|
|
319
|
+
// grammar so the operator walkthrough's line citations stay put.
|
|
320
|
+
/** What a confirmed write buys, from the command's own table — never a phrase of this file's own. */
|
|
321
|
+
function consequenceLines(verb, park, release, disposition) {
|
|
322
|
+
const lines = [`consequence disposition ${disposition}; appends one task-approved${release ? ` with release ${release}` : " with no release"}`];
|
|
323
|
+
if (verb === "waive")
|
|
324
|
+
lines.push(` marks gate ${park.failedGate} satisfied; it does NOT mark the task done`);
|
|
325
|
+
if (verb === "recheck")
|
|
326
|
+
lines.push(" marks no gate satisfied; the whole declared battery re-runs");
|
|
327
|
+
if (verb === "uphold")
|
|
328
|
+
lines.push(" sides with the reviewer; funds one fixed attempt carrying the findings");
|
|
329
|
+
lines.push(` it will ${APPROVAL_ENACTS[disposition]}`);
|
|
330
|
+
return lines;
|
|
331
|
+
}
|
|
332
|
+
/** Who enacts it — the three lock states, each stated by the command's own sentence. */
|
|
333
|
+
export function enactmentLines(run, disposition) {
|
|
334
|
+
const owner = run.live
|
|
335
|
+
? "enactment matching live daemon — pending daemon enactment at its next task boundary"
|
|
336
|
+
: run.blockingRunId
|
|
337
|
+
? `enactment other live run \`${run.blockingRunId}\` holds the repository lock — recorded, not dispatched`
|
|
338
|
+
: `enactment no live owner — recorded, not dispatched; resume required: tickmarkr resume ${run.runId}`;
|
|
339
|
+
return [owner, ` ${approvalEnactment(disposition, run)}`];
|
|
340
|
+
}
|
|
@@ -5,6 +5,7 @@ import { channelKey } from "../../adapters/types.js";
|
|
|
5
5
|
import { newestPark, permittedDecisionVerbs } from "../../cli/commands/approve.js";
|
|
6
6
|
import { parseOwnedName } from "../../drivers/types.js";
|
|
7
7
|
import { projectActivity } from "../../run/activity.js";
|
|
8
|
+
import { effectiveDecisions, withPhysicalLine } from "../../run/journal.js";
|
|
8
9
|
import { isPidLive } from "../../run/lock.js";
|
|
9
10
|
import { normalizeGateOutcome } from "../../run/outcome.js";
|
|
10
11
|
import { projectOperatorSummary } from "../../run/operator-summary.js";
|
|
@@ -79,6 +80,15 @@ function parseSource(raw) {
|
|
|
79
80
|
}
|
|
80
81
|
return { events, defects };
|
|
81
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* OBS-1178: the rows the task-state folds read. A refused or unsound decision released nothing, so its
|
|
85
|
+
* task-approved row is dropped here; the one decision fold keys every row by its physical line. The
|
|
86
|
+
* journal rows the cockpit draws keep every line as evidence.
|
|
87
|
+
*/
|
|
88
|
+
function decidedEvents(events) {
|
|
89
|
+
const effective = effectiveDecisions(events.map((event) => withPhysicalLine(event, event.line)));
|
|
90
|
+
return events.filter((event) => event.event !== "task-approved" || effective.has(event.line));
|
|
91
|
+
}
|
|
82
92
|
function assignmentFrom(value) {
|
|
83
93
|
const assignment = recordValue(value);
|
|
84
94
|
return assignment
|
|
@@ -844,7 +854,8 @@ export function deriveRunCockpitData(source, binaryVersion, options = {}) {
|
|
|
844
854
|
// alive in the lock — the cockpit called a live run interrupted.
|
|
845
855
|
: pid !== undefined && (options.isDaemonAlive ?? isPidLive)(pid);
|
|
846
856
|
const interrupted = !alive;
|
|
847
|
-
const
|
|
857
|
+
const decided = decidedEvents(events);
|
|
858
|
+
const tasks = deriveTasks(decided, interrupted);
|
|
848
859
|
const taskFacts = [...tasks.values()];
|
|
849
860
|
const done = taskFacts.filter((task) => task.state === "done").length;
|
|
850
861
|
const failed = taskFacts.filter((task) => task.state === "failed").length;
|
|
@@ -873,7 +884,7 @@ export function deriveRunCockpitData(source, binaryVersion, options = {}) {
|
|
|
873
884
|
// run-end — the run's own closing statement is enough.
|
|
874
885
|
const runStatus = failed > 0
|
|
875
886
|
? "failed"
|
|
876
|
-
: human > 0 || unresolvedSummaryParks(
|
|
887
|
+
: human > 0 || unresolvedSummaryParks(decided).length > 0
|
|
877
888
|
? "parked"
|
|
878
889
|
: lifecycle === "superseded" || hasInterruptedTask
|
|
879
890
|
? "interrupted"
|
|
@@ -46,5 +46,15 @@ export interface ConsolidatedOptions {
|
|
|
46
46
|
environment?: NodeJS.ProcessEnv;
|
|
47
47
|
diagnostics?: readonly HomeNeedsYouTarget[];
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* OBS-1178: the store's incremental fold reads every task-approved row as a release. A refused or unsound
|
|
51
|
+
* decision released nothing, so whenever the rows the store has read hold one — whatever state the raw
|
|
52
|
+
* fold shows now, since a later run-end restores a park but not the gate evidence a rejected recheck
|
|
53
|
+
* cleared — the operator snapshot is refolded over the decided rows, each keeping its physical line and
|
|
54
|
+
* the store's evidence identity.
|
|
55
|
+
* ponytail: a whole-journal read per observed journal change (parsed only when it holds an approval);
|
|
56
|
+
* move the decision fold into OperatorStateFold if that ever measures slow.
|
|
57
|
+
*/
|
|
58
|
+
export declare function decidedLiveStore(store: LiveStore): LiveStore;
|
|
49
59
|
/** The live entry mounts the dependency-owned leaves, using only C2 snapshots/history/page. */
|
|
50
60
|
export declare function runConsolidatedCockpit(options: ConsolidatedOptions): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { existsSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { renderMarkdownRecord } from "../../cli/commands/report.js";
|
|
5
5
|
import { loadConfig } from "../../config/config.js";
|
|
@@ -20,9 +20,10 @@ import { createPointerReportReader, POINTER_TRACKING_ON, POINTER_TRACKING_OFF }
|
|
|
20
20
|
import { deriveRunDecisions, previewDecision, executeDecision, withDecisionPreview, withDecisionReceipt, decisionConfirmLines } from "./decision-actions.js";
|
|
21
21
|
import { approvalRunOwner } from "../../cli/commands/approve.js";
|
|
22
22
|
import { GLYPHS } from "../../brand.js";
|
|
23
|
-
import { Journal } from "../../run/journal.js";
|
|
23
|
+
import { effectiveEvents, Journal, parseJournalText, physicalLine } from "../../run/journal.js";
|
|
24
24
|
import { observeNamedRun, WATCH_OWNER_ENV } from "../../run/supervision.js";
|
|
25
25
|
import { formatOwnedName } from "../../drivers/types.js";
|
|
26
|
+
import { readOperatorState } from "../../run/operator-state.js";
|
|
26
27
|
import { cellWidth, wrapCells } from "./width.js";
|
|
27
28
|
import { resolveShellColourMode } from "./theme.js";
|
|
28
29
|
let timelineUsers = 0;
|
|
@@ -58,6 +59,52 @@ export function borrowRuntimeTimeline() {
|
|
|
58
59
|
}
|
|
59
60
|
export const SHELL_TERMINAL_ENTER = "\x1b[?1049h\x1b[22;0t\x1b]0;tickmarkr cockpit\x07" + POINTER_TRACKING_ON;
|
|
60
61
|
export const SHELL_TERMINAL_RESTORE = POINTER_TRACKING_OFF + "\x1b[23;0t\x1b[?25h\x1b[?1049l";
|
|
62
|
+
/**
|
|
63
|
+
* OBS-1178: the store's incremental fold reads every task-approved row as a release. A refused or unsound
|
|
64
|
+
* decision released nothing, so whenever the rows the store has read hold one — whatever state the raw
|
|
65
|
+
* fold shows now, since a later run-end restores a park but not the gate evidence a rejected recheck
|
|
66
|
+
* cleared — the operator snapshot is refolded over the decided rows, each keeping its physical line and
|
|
67
|
+
* the store's evidence identity.
|
|
68
|
+
* ponytail: a whole-journal read per observed journal change (parsed only when it holds an approval);
|
|
69
|
+
* move the decision fold into OperatorStateFold if that ever measures slow.
|
|
70
|
+
*/
|
|
71
|
+
export function decidedLiveStore(store) {
|
|
72
|
+
let key;
|
|
73
|
+
let operator;
|
|
74
|
+
const decided = (snap) => {
|
|
75
|
+
const { journal, graph } = snap;
|
|
76
|
+
const observed = `${journal.source}:${journal.generation}:${journal.offset}:${graph.identity}:${graph.status}`;
|
|
77
|
+
if (observed !== key) {
|
|
78
|
+
key = observed;
|
|
79
|
+
operator = undefined;
|
|
80
|
+
let bytes;
|
|
81
|
+
try {
|
|
82
|
+
bytes = readFileSync(journal.source).subarray(0, journal.offset);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return snap;
|
|
86
|
+
}
|
|
87
|
+
if (bytes.includes("task-approved")) {
|
|
88
|
+
const rows = parseJournalText(bytes.subarray(0, bytes.lastIndexOf(10) + 1).toString("utf8"))
|
|
89
|
+
.filter(e => e !== null && typeof e === "object" && typeof e.event === "string" && typeof e.data === "object" && e.data !== null);
|
|
90
|
+
const kept = effectiveEvents(rows);
|
|
91
|
+
if (kept.length !== rows.length) {
|
|
92
|
+
const events = kept.map((event, i) => {
|
|
93
|
+
const line = physicalLine(kept, i);
|
|
94
|
+
return { source: journal.source, line, id: `${journal.source}#L${line}`, generation: journal.generation, event };
|
|
95
|
+
});
|
|
96
|
+
operator = readOperatorState({ events, graph: graph.status === "readable" ? graph.value : undefined,
|
|
97
|
+
readable: journal.status === "readable" && journal.backlogBytes === 0 });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!operator)
|
|
102
|
+
return snap;
|
|
103
|
+
const { sequence, observedAt, graphAvailability } = snap.operator;
|
|
104
|
+
return { ...snap, operator: { ...operator, sequence, observedAt, graphAvailability } };
|
|
105
|
+
};
|
|
106
|
+
return { ...store, snapshot: () => decided(store.snapshot()), refresh: () => decided(store.refresh()) };
|
|
107
|
+
}
|
|
61
108
|
/** Preserve a policy-disabled gate from C2 even when C5's generic event
|
|
62
109
|
* mapper calls an unpassed result unknown. Lead with the gate name so a long
|
|
63
110
|
* task identity cannot clip the label that explains the disabled glyph. */
|
|
@@ -97,7 +144,7 @@ export async function runConsolidatedCockpit(options) {
|
|
|
97
144
|
output.write(SHELL_TERMINAL_ENTER);
|
|
98
145
|
}
|
|
99
146
|
store = createLiveStore({ cwd, runId, now: options.now });
|
|
100
|
-
const source = store;
|
|
147
|
+
const source = decidedLiveStore(store);
|
|
101
148
|
if (options.observeRun !== false)
|
|
102
149
|
observation = observeNamedRun(cwd, runId, options.environment);
|
|
103
150
|
// BD-1 (RULING-231-19 §2): the daemon-placed board carries the watch owner token and mounts
|
|
@@ -34,6 +34,9 @@ export declare function deriveRunViewRows(data: RunCockpitData, viewId: RunViewI
|
|
|
34
34
|
* task row — identity, phase, build evidence, blocker, next action — each unrecorded field saying so,
|
|
35
35
|
* each recorded one citing the journal line it was read from, and the OBS-1048 stall marker when the
|
|
36
36
|
* newest attempt's harvest is suspect. No attempt label is printed here: the ruler is the row's own.
|
|
37
|
+
* A task with no recorded dispatch folds identity, phase, and build into one missing-evidence clause
|
|
38
|
+
* (OBS-1104). A recorded blocker or next action — a pre-dispatch park, a dependency wait — stays,
|
|
39
|
+
* cited to the journal row that produced it.
|
|
37
40
|
*/
|
|
38
41
|
export declare function taskProjectionText(row: TaskRow, journal?: readonly JournalRow[]): string;
|
|
39
42
|
/**
|