bareloop 0.14.0 → 0.16.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/CHANGELOG.md +218 -0
- package/bareloop.context.md +374 -49
- package/package.json +2 -2
- package/src/authorjob.js +2 -2
- package/src/codeversion.js +155 -0
- package/src/index.js +12 -0
- package/src/ledger.js +1 -1
- package/src/promptregisters.js +93 -0
- package/src/replay.js +1212 -0
- package/src/reuse.js +11 -13
- package/src/run.js +14 -0
- package/types/codeversion.d.ts +18 -0
- package/types/index.d.ts +2 -0
- package/types/ledger.d.ts +14 -0
- package/types/promptregisters.d.ts +31 -0
- package/types/replay.d.ts +195 -0
- package/types/reuse.d.ts +10 -11
package/src/reuse.js
CHANGED
|
@@ -46,7 +46,7 @@ import { existsSync } from 'node:fs';
|
|
|
46
46
|
import { join } from 'node:path';
|
|
47
47
|
import { createHash } from 'node:crypto';
|
|
48
48
|
import { createRequire } from 'node:module';
|
|
49
|
-
import { loadRegistry, loadBridge, saveBridge, appendGreen, appendRed, mintBridge, registryExists, reuseEligibility, recordDoor,
|
|
49
|
+
import { loadRegistry, loadBridge, saveBridge, appendGreen, appendRed, mintBridge, registryExists, reuseEligibility, recordDoor, QUARANTINED_CODE } from './bridges.js';
|
|
50
50
|
import { renderListing, selectionPrompt } from './selection.js';
|
|
51
51
|
import { closeStagesOf } from './plan.js';
|
|
52
52
|
import { MIN_WALL_MS, jobSpecHash } from './job.js';
|
|
@@ -2128,7 +2128,7 @@ export function writeGreenRow({ registryDir, bridge = null, meta, record }) {
|
|
|
2128
2128
|
* `no-row-for-run`. This is the row, and STORAGE IS ALL IT IS — selection, promotion
|
|
2129
2129
|
* and reuse execution stay parked (layer-3-reuse). Nothing here reads a row back.
|
|
2130
2130
|
*
|
|
2131
|
-
*
|
|
2131
|
+
* THREE REFUSALS, each one a guard that already existed somewhere and is kept:
|
|
2132
2132
|
* - NO REGISTRY, no row. The registry path is operator-supplied and never
|
|
2133
2133
|
* conjured; without one the door says, in words, that nothing was released.
|
|
2134
2134
|
* - ALREADY-GREEN mints nothing. The close was satisfied before this run did
|
|
@@ -2136,16 +2136,15 @@ export function writeGreenRow({ registryDir, bridge = null, meta, record }) {
|
|
|
2136
2136
|
* the writing side rather than the door's.
|
|
2137
2137
|
* - A GREEN WITH NO PLAN mints nothing. The artifact that inherits is the one that
|
|
2138
2138
|
* RAN, and there is none (R1).
|
|
2139
|
-
*
|
|
2140
|
-
*
|
|
2141
|
-
*
|
|
2142
|
-
*
|
|
2143
|
-
*
|
|
2144
|
-
*
|
|
2145
|
-
*
|
|
2146
|
-
*
|
|
2147
|
-
*
|
|
2148
|
-
* `verdictType` off the record. Nothing here sets the flag.
|
|
2139
|
+
*
|
|
2140
|
+
* hamr's ruling 2026-08-30 (PRD open item #16, "green-class registry hole"): a
|
|
2141
|
+
* green-class run (mechanical close) now MINTS its row too, born RELEASED — a
|
|
2142
|
+
* machine proved it, so there is no credit to quarantine. This drops the fourth
|
|
2143
|
+
* refusal this function used to carry (`credit-not-held`, main's behaviour kept
|
|
2144
|
+
* from before the ruling): every outcome that reaches this line and clears the
|
|
2145
|
+
* three refusals above now mints, held or not. Whether the minted row IS held is
|
|
2146
|
+
* decided one level down, in `greenParts`, off `quarantinesCredit(verdictType)` —
|
|
2147
|
+
* the ONE spelling of "is this class held" — never here.
|
|
2149
2148
|
* @param {{registryDir: string|null|undefined, job: any, name: string|null,
|
|
2150
2149
|
* outcome: string, plan: any, record: any}} o
|
|
2151
2150
|
* @returns {{minted: boolean, reason: string|null, write: any|null}}
|
|
@@ -2156,7 +2155,6 @@ export function writeRunGreenRow({ registryDir, job, name, outcome, plan, record
|
|
|
2156
2155
|
return { minted: false, reason: outcome === 'already-green' ? 'green-predates-run' : 'not-green', write: null };
|
|
2157
2156
|
}
|
|
2158
2157
|
if (!isObj(plan)) return { minted: false, reason: 'no-plan-executed', write: null };
|
|
2159
|
-
if (!quarantinesCredit(job?.verdictType)) return { minted: false, reason: 'credit-not-held', write: null };
|
|
2160
2158
|
|
|
2161
2159
|
const target = isNonEmptyString(name) ? name : job.job;
|
|
2162
2160
|
const meta = {
|
package/src/run.js
CHANGED
|
@@ -17,6 +17,7 @@ import { validateJob, jobSpecHash, checkApproval } from './job.js';
|
|
|
17
17
|
import { runPlan } from './planrun.js';
|
|
18
18
|
import { SMOKE_STORE, resolveHumanRuling } from './kinds.js';
|
|
19
19
|
import { readShimArm } from './readshim.js';
|
|
20
|
+
import { codeVersion } from './codeversion.js';
|
|
20
21
|
|
|
21
22
|
/** @typedef {{code: string, path: string, detail?: string}} Red */
|
|
22
23
|
|
|
@@ -303,6 +304,19 @@ export async function runJob(rawSpec, { approvals, workdir, provider, nativeProv
|
|
|
303
304
|
emit('job-start', {
|
|
304
305
|
job: job.job, specHash: jobSpecHash(job), budgetUsd: job.budgetUsd,
|
|
305
306
|
shape: 'plan', goal: job.goal,
|
|
307
|
+
// F117: replay reads these; report-only. `verdictType` is a REQUIRED spec
|
|
308
|
+
// field (src/job.js:552 reds `missing-required` before this line is ever
|
|
309
|
+
// reached), so it is always a real validated string here, never a
|
|
310
|
+
// fabricated default. `model` rides along only when the shell-owned
|
|
311
|
+
// provider binding actually carries one (bare-agent's Loop reads
|
|
312
|
+
// `baseProvider.model` directly — src/planrun.js:76's own comment); a
|
|
313
|
+
// native/clipipe call path with no such provider omits the field rather
|
|
314
|
+
// than guessing.
|
|
315
|
+
verdictType: job.verdictType,
|
|
316
|
+
...(typeof provider?.model === 'string' ? { model: provider.model } : {}),
|
|
317
|
+
// F118: replay reads this; report-only. `dirty` is deliberately omitted —
|
|
318
|
+
// it can't be known without a shell, and null = unknown, never faked.
|
|
319
|
+
code: (() => { const { version, sha } = codeVersion(); return { version, sha }; })(),
|
|
306
320
|
// RESUME (v1.46 §3) — the DECLARED fold, the `try-start` precedent one level
|
|
307
321
|
// down. A reader reconstructing a chain of resumes must add only each attempt's
|
|
308
322
|
// OWN new rounds; without this record the second resume of a halted run would
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report-only stamp of the bareloop code this process is running as. Never
|
|
3
|
+
* throws — an unreadable package.json or absent `.git` reads as `null`,
|
|
4
|
+
* honestly, rather than crashing a caller over a report field.
|
|
5
|
+
* @returns {{ version: string|null, sha: string|null, dirty: null }}
|
|
6
|
+
*/
|
|
7
|
+
export function codeVersion(): {
|
|
8
|
+
version: string | null;
|
|
9
|
+
sha: string | null;
|
|
10
|
+
dirty: null;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* First 8 characters of a sha, or null. Mirrors the short-sha convention
|
|
14
|
+
* `replay.js` already uses for `specHash` (`s.specHash.slice(0, 8)`).
|
|
15
|
+
* @param {string|null|undefined} sha
|
|
16
|
+
* @returns {string|null}
|
|
17
|
+
*/
|
|
18
|
+
export function shortSha(sha: string | null | undefined): string | null;
|
package/types/index.d.ts
CHANGED
|
@@ -23,4 +23,6 @@ export { scrubRaw, RAW_PERSIST_MAX, RAW_TRIM_MARKER } from "./text.js";
|
|
|
23
23
|
export { authorClose, authorPrompt, declarationSchema, makeLoopGenerate, MAX_REVISIONS, QUESTION_SETS, GREEN_QUESTIONS, SOFTGREEN_QUESTIONS, CLASS_STATEMENTS, questionsFor, requiredAnswersFor } from "./authorflow.js";
|
|
24
24
|
export { validateCloseDecl, runDeclaredStages, declaredStages, isDeclaredClose, guardNames, closeGrade, DECLARED_GENRES, DECLARED_GAP_PREFIX, DECLARED_GAP_KEEP, DECLARED_CLOSE_CLASSES, CLOSE_DECL_FIELDS, HUMAN_PAUSE, HITL_PAUSE, HITL_DECISION_RED, HUMAN_CHECKPOINTS } from "./declaredclose.js";
|
|
25
25
|
export { runBehaviour, formatBehaviour } from "./behaviour.js";
|
|
26
|
+
export { replayRun, formatReplay } from "./replay.js";
|
|
26
27
|
export { runInterview, authorCloseForJob, assembleSpec, prepareSigning, refusalEvents, refuseLockedKind, GENRE, REFUSAL_LIB, REFUSAL_CATEGORY, AUTHORED_SPEC_FIELDS } from "./authorjob.js";
|
|
28
|
+
export { PROMPT_REGISTERS, isPromptFile } from "./promptregisters.js";
|
package/types/ledger.d.ts
CHANGED
|
@@ -103,6 +103,20 @@ export const LEDGER_CLASSES: readonly string[];
|
|
|
103
103
|
* reads as a GUESS rather than as vouched. The fail-safe direction, by construction.
|
|
104
104
|
*/
|
|
105
105
|
export const VOUCHED_RATE_SOURCES: readonly string[];
|
|
106
|
+
/** The spend records a run's money is read from. `worker-round` and `judge-round` are the
|
|
107
|
+
* ACCOUNTED types — exactly `ACCOUNTED_ROUND_TYPES` in src/run.js, which is the ledger that
|
|
108
|
+
* enforces the budget (F12). `judge-round` is the softgreen rung's judged floor: the first
|
|
109
|
+
* spend a CLOSE has ever had, and a provenance readout that skipped it would be blind to the
|
|
110
|
+
* one seam whose rate nobody has audited. `worker-turn` is the native surface's per-turn
|
|
111
|
+
* attribution — unpriced by design, included so the readout covers every metering row the
|
|
112
|
+
* plan flow writes rather than only the ones carrying money. The attempt-level totals
|
|
113
|
+
* (`worker-result`, `worker-plan`) are ECHOES of these same rounds and are excluded, for
|
|
114
|
+
* the same reason the ledger in run.js excludes them: counting them double-counts.
|
|
115
|
+
*
|
|
116
|
+
* Re-spelled rather than imported (src/run.js imports this module's siblings and a cycle
|
|
117
|
+
* buys nothing here) — `tests/ledger.test.js` asserts the two spellings agree, because a
|
|
118
|
+
* spend-slicing instrument that misses a writer is the F45 class this list exists to avoid. */
|
|
119
|
+
export const SPEND_RECORD_TYPES: readonly string[];
|
|
106
120
|
export type ProvenanceBucket = {
|
|
107
121
|
rounds: number;
|
|
108
122
|
usd: number;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when `path` names one of the inventoried prompt-register files. Accepts
|
|
3
|
+
* either a repo-root-relative path (`src/tools.js`) or an absolute/relative
|
|
4
|
+
* path ending in one (`/home/x/bareloop/src/tools.js`, `./src/tools.js`) — git
|
|
5
|
+
* hands the check root-relative paths, but a caller working from an absolute
|
|
6
|
+
* path should not have to strip the prefix itself.
|
|
7
|
+
* @param {string} path
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
export function isPromptFile(path: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* One entry in the prompt-register inventory: a file known to carry
|
|
13
|
+
* model-facing prompt/instruction text, and the name(s) that live there for a
|
|
14
|
+
* human reading this file. `name` may point at a named const OR at a function
|
|
15
|
+
* that builds a prompt inline (e.g. `scoutPrompt()`) — the check only ever
|
|
16
|
+
* keys on `file`.
|
|
17
|
+
* @typedef {{file: string, name: string}} PromptRegisterEntry
|
|
18
|
+
*/
|
|
19
|
+
/** @type {ReadonlyArray<PromptRegisterEntry>} */
|
|
20
|
+
export const PROMPT_REGISTERS: ReadonlyArray<PromptRegisterEntry>;
|
|
21
|
+
/**
|
|
22
|
+
* One entry in the prompt-register inventory: a file known to carry
|
|
23
|
+
* model-facing prompt/instruction text, and the name(s) that live there for a
|
|
24
|
+
* human reading this file. `name` may point at a named const OR at a function
|
|
25
|
+
* that builds a prompt inline (e.g. `scoutPrompt()`) — the check only ever
|
|
26
|
+
* keys on `file`.
|
|
27
|
+
*/
|
|
28
|
+
export type PromptRegisterEntry = {
|
|
29
|
+
file: string;
|
|
30
|
+
name: string;
|
|
31
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconstruct one run's whole story from its spine + gate-audit. Pure: no
|
|
3
|
+
* IO, no spine writes, no verdict. Malformed/non-object entries in either
|
|
4
|
+
* array are skipped and counted, never thrown on.
|
|
5
|
+
*
|
|
6
|
+
* A step id can recur (a replan re-executes the same step): `steps[].occurrence`
|
|
7
|
+
* is 1 for that id's first step-start..step-end pair, 2 for its second, etc.
|
|
8
|
+
* Every field on an occurrence — rounds, toolCalls, checks, treeChanged, wallMs,
|
|
9
|
+
* spentUsd — is scoped to records strictly between ITS OWN start/end `seq`,
|
|
10
|
+
* never pooled across every occurrence sharing the id.
|
|
11
|
+
*
|
|
12
|
+
* A run whose spine carries no `step-start` at all (the plan's only step was
|
|
13
|
+
* already-green-skipped on a resumed leg, or a genuinely legacy loop-only
|
|
14
|
+
* shape) has no STEPS timeline to build — `timelineKind` reads `'iterations'`
|
|
15
|
+
* and `iterations[]` is populated from `iteration-start`/`close-verdict`/
|
|
16
|
+
* `run-end`/`escalation` instead. `steps`/`iterations` are never both
|
|
17
|
+
* populated: exactly one is non-empty, selected by `timelineKind`.
|
|
18
|
+
*
|
|
19
|
+
* @param {any[]} spineEvents parsed records from `u-<id>.jsonl`
|
|
20
|
+
* @param {any[]} [auditEvents] parsed records from the sibling `-gate-audit.jsonl`
|
|
21
|
+
* @param {{runId?: string|null}} [opts] `runId`: caller-supplied (the spine
|
|
22
|
+
* carries no run-id field of its own — see the file header)
|
|
23
|
+
*/
|
|
24
|
+
export function replayRun(spineEvents: any[], auditEvents?: any[], { runId }?: {
|
|
25
|
+
runId?: string | null;
|
|
26
|
+
}): {
|
|
27
|
+
runId: string | null;
|
|
28
|
+
job: any;
|
|
29
|
+
verdictType: any;
|
|
30
|
+
model: any;
|
|
31
|
+
code: {
|
|
32
|
+
version: any;
|
|
33
|
+
sha: any;
|
|
34
|
+
} | null;
|
|
35
|
+
goal: any;
|
|
36
|
+
lastEscalation: any;
|
|
37
|
+
budgetUsd: any;
|
|
38
|
+
specHash: any;
|
|
39
|
+
branch: any;
|
|
40
|
+
outcome: any;
|
|
41
|
+
stopReason: string | null;
|
|
42
|
+
spentUsd: any;
|
|
43
|
+
spendComplete: boolean;
|
|
44
|
+
floorReasons: string[];
|
|
45
|
+
transportRetryCount: number;
|
|
46
|
+
transportRetriesOutsideWindows: number;
|
|
47
|
+
wallMs: number | null;
|
|
48
|
+
chainClock: {
|
|
49
|
+
source: "wall-halt" | "wall-clock";
|
|
50
|
+
elapsedMs: number;
|
|
51
|
+
requestedMs: number | null;
|
|
52
|
+
enforcedMs: number | null;
|
|
53
|
+
bounded: boolean;
|
|
54
|
+
} | null;
|
|
55
|
+
resumed: boolean;
|
|
56
|
+
resumeSeed: {
|
|
57
|
+
phase: any;
|
|
58
|
+
completed: any;
|
|
59
|
+
skipping: any;
|
|
60
|
+
divergence: any;
|
|
61
|
+
} | null;
|
|
62
|
+
thisFileSpend: {
|
|
63
|
+
value: any;
|
|
64
|
+
source: "engagementSpentUsd" | "summed rounds";
|
|
65
|
+
totalRounds: number;
|
|
66
|
+
unpricedRounds: number;
|
|
67
|
+
};
|
|
68
|
+
spendMismatch: {
|
|
69
|
+
spentUsd: any;
|
|
70
|
+
thisFileUsd: any;
|
|
71
|
+
diffUsd: number;
|
|
72
|
+
} | null;
|
|
73
|
+
timelineKind: "steps" | "iterations";
|
|
74
|
+
steps: {
|
|
75
|
+
checks: {
|
|
76
|
+
passed: number;
|
|
77
|
+
failed: number;
|
|
78
|
+
};
|
|
79
|
+
treeChanged: boolean;
|
|
80
|
+
rounds: number;
|
|
81
|
+
toolCalls: number;
|
|
82
|
+
wallMs: number | null;
|
|
83
|
+
spentUsd: number | null;
|
|
84
|
+
unpricedRounds: number;
|
|
85
|
+
tripped: {
|
|
86
|
+
category: any;
|
|
87
|
+
detail: string | null;
|
|
88
|
+
} | null;
|
|
89
|
+
transportRetries: {
|
|
90
|
+
count: number;
|
|
91
|
+
label: string;
|
|
92
|
+
error: string | null;
|
|
93
|
+
} | null;
|
|
94
|
+
id: any;
|
|
95
|
+
occurrence: any;
|
|
96
|
+
outcome: any;
|
|
97
|
+
}[];
|
|
98
|
+
iterations: {
|
|
99
|
+
rounds: number;
|
|
100
|
+
toolCalls: number;
|
|
101
|
+
wallMs: number | null;
|
|
102
|
+
spentUsd: number | null;
|
|
103
|
+
unpricedRounds: number;
|
|
104
|
+
tripped: {
|
|
105
|
+
category: any;
|
|
106
|
+
detail: string | null;
|
|
107
|
+
} | null;
|
|
108
|
+
transportRetries: {
|
|
109
|
+
count: number;
|
|
110
|
+
label: string;
|
|
111
|
+
error: string | null;
|
|
112
|
+
} | null;
|
|
113
|
+
iteration: any;
|
|
114
|
+
verdict: any;
|
|
115
|
+
boundary: any;
|
|
116
|
+
closeStage: {
|
|
117
|
+
verdict: any;
|
|
118
|
+
stages: any;
|
|
119
|
+
} | null;
|
|
120
|
+
}[];
|
|
121
|
+
replans: any;
|
|
122
|
+
close: {
|
|
123
|
+
source: "close-verdict" | "outer-close";
|
|
124
|
+
iteration: number | null;
|
|
125
|
+
verdict: string | null;
|
|
126
|
+
stages: any[] | null;
|
|
127
|
+
} | null;
|
|
128
|
+
ending: {
|
|
129
|
+
record: any;
|
|
130
|
+
before: any[];
|
|
131
|
+
escalation: any;
|
|
132
|
+
};
|
|
133
|
+
behaviour: {
|
|
134
|
+
totalCalls: number;
|
|
135
|
+
byTool: Record<string, number>;
|
|
136
|
+
denied: number;
|
|
137
|
+
uniquePaths: number;
|
|
138
|
+
repeats: number;
|
|
139
|
+
repeatPct: number | null;
|
|
140
|
+
};
|
|
141
|
+
memoryCache: any;
|
|
142
|
+
skipped: number;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Render a {@link replayRun} summary as one printable page: a header block
|
|
146
|
+
* (goal/shape/signed/branch/outcome/spent), a timeline (steps or iterations,
|
|
147
|
+
* whichever the spine has), the CLOSE section (never omitted), the ending
|
|
148
|
+
* (last record + 3 before it, plus an out-of-window escalation), the
|
|
149
|
+
* behaviour block, and the MEMORY-CACHE line (always printed — `not armed on
|
|
150
|
+
* this run` when there is no record, never silently absent).
|
|
151
|
+
* @param {ReturnType<typeof replayRun>} summary
|
|
152
|
+
* @returns {string}
|
|
153
|
+
*/
|
|
154
|
+
export function formatReplay(summary: ReturnType<typeof replayRun>): string;
|
|
155
|
+
/**
|
|
156
|
+
* One `--all` row's raw field strings for one real spine:
|
|
157
|
+
* `id job shape outcome spend wall steps reason`. `reason` is
|
|
158
|
+
* `category — <first clause of detail, cut at 60 chars>` off the last
|
|
159
|
+
* escalation — never the generic `decision` prose. `-` on a green row;
|
|
160
|
+
* `no job-end (killed mid-run)` when the spine never reached a `job-end` at
|
|
161
|
+
* all (outcome itself is `null`) — a distinct, named case, not folded into
|
|
162
|
+
* the ordinary escalation reason.
|
|
163
|
+
* @param {ReturnType<typeof replayRun>} summary
|
|
164
|
+
* @returns {{id: string, resumed: boolean, job: string, shape: string, class: string, model: string, outcome: string, hadTransportRetries: boolean, spend: string, wall: string, steps: string, reason: string}}
|
|
165
|
+
*/
|
|
166
|
+
export function summarizeForAllLine(summary: ReturnType<typeof replayRun>): {
|
|
167
|
+
id: string;
|
|
168
|
+
resumed: boolean;
|
|
169
|
+
job: string;
|
|
170
|
+
shape: string;
|
|
171
|
+
class: string;
|
|
172
|
+
model: string;
|
|
173
|
+
outcome: string;
|
|
174
|
+
hadTransportRetries: boolean;
|
|
175
|
+
spend: string;
|
|
176
|
+
wall: string;
|
|
177
|
+
steps: string;
|
|
178
|
+
reason: string;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Render every row {@link summarizeForAllLine} built for one directory's
|
|
182
|
+
* spines as a fixed-column, aligned table WITH a header row (plus any
|
|
183
|
+
* `<name> not-a-spine` lines interleaved at their original position — those
|
|
184
|
+
* are not part of the aligned columns, since a filename carries no outcome/
|
|
185
|
+
* spend/wall of its own). Never CSV: columns are padded, not delimited.
|
|
186
|
+
* @param {Array<{kind: 'spine', row: ReturnType<typeof summarizeForAllLine>}|{kind: 'not-a-spine', name: string}>} entries
|
|
187
|
+
* @returns {string}
|
|
188
|
+
*/
|
|
189
|
+
export function formatAllLines(entries: Array<{
|
|
190
|
+
kind: "spine";
|
|
191
|
+
row: ReturnType<typeof summarizeForAllLine>;
|
|
192
|
+
} | {
|
|
193
|
+
kind: "not-a-spine";
|
|
194
|
+
name: string;
|
|
195
|
+
}>): string;
|
package/types/reuse.d.ts
CHANGED
|
@@ -470,7 +470,7 @@ export function writeGreenRow({ registryDir, bridge, meta, record }: {
|
|
|
470
470
|
* `no-row-for-run`. This is the row, and STORAGE IS ALL IT IS — selection, promotion
|
|
471
471
|
* and reuse execution stay parked (layer-3-reuse). Nothing here reads a row back.
|
|
472
472
|
*
|
|
473
|
-
*
|
|
473
|
+
* THREE REFUSALS, each one a guard that already existed somewhere and is kept:
|
|
474
474
|
* - NO REGISTRY, no row. The registry path is operator-supplied and never
|
|
475
475
|
* conjured; without one the door says, in words, that nothing was released.
|
|
476
476
|
* - ALREADY-GREEN mints nothing. The close was satisfied before this run did
|
|
@@ -478,16 +478,15 @@ export function writeGreenRow({ registryDir, bridge, meta, record }: {
|
|
|
478
478
|
* the writing side rather than the door's.
|
|
479
479
|
* - A GREEN WITH NO PLAN mints nothing. The artifact that inherits is the one that
|
|
480
480
|
* RAN, and there is none (R1).
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
* `verdictType` off the record. Nothing here sets the flag.
|
|
481
|
+
*
|
|
482
|
+
* hamr's ruling 2026-08-30 (PRD open item #16, "green-class registry hole"): a
|
|
483
|
+
* green-class run (mechanical close) now MINTS its row too, born RELEASED — a
|
|
484
|
+
* machine proved it, so there is no credit to quarantine. This drops the fourth
|
|
485
|
+
* refusal this function used to carry (`credit-not-held`, main's behaviour kept
|
|
486
|
+
* from before the ruling): every outcome that reaches this line and clears the
|
|
487
|
+
* three refusals above now mints, held or not. Whether the minted row IS held is
|
|
488
|
+
* decided one level down, in `greenParts`, off `quarantinesCredit(verdictType)` —
|
|
489
|
+
* the ONE spelling of "is this class held" — never here.
|
|
491
490
|
* @param {{registryDir: string|null|undefined, job: any, name: string|null,
|
|
492
491
|
* outcome: string, plan: any, record: any}} o
|
|
493
492
|
* @returns {{minted: boolean, reason: string|null, write: any|null}}
|