clearotron 0.2.4 → 0.3.0-beta.1
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/.env.example +13 -2
- package/CONTRIBUTING.md +1 -1
- package/INSTALL.md +62 -38
- package/bin/brandowner.mjs +18 -169
- package/bin/clearotron.mjs +3 -1
- package/bin/connect.mjs +28 -19
- package/bin/disconnect.mjs +3 -3
- package/bin/example.mjs +7 -7
- package/bin/framework-preflight.mjs +49 -0
- package/bin/grant.mjs +151 -93
- package/bin/onboard.mjs +220 -138
- package/bin/start.mjs +146 -137
- package/bin/stop.mjs +2 -2
- package/bin/update.mjs +1 -1
- package/build-info.json +2 -2
- package/docs/CLIENT-MCP.md +2 -2
- package/docs/E2E.md +12 -2
- package/docs/ONBOARDING.md +1 -1
- package/docs/PORTAL.md +14 -13
- package/docs/SECURITY.md +23 -24
- package/docs/architecture/04-configuration-reference.md +12 -5
- package/docs/architecture/05-config-governance.md +7 -7
- package/docs/architecture/07-quality-and-audit.md +1 -1
- package/docs/architecture/08-development-guide.md +5 -0
- package/docs/configuration.md +118 -0
- package/docs/decisions/0004-documentation-structure.md +2 -2
- package/docs/decisions/0006-what-the-public-repository-carries.md +2 -2
- package/driver/CHANGELOG.md +70 -0
- package/driver/ask-ledger.mjs +2 -2
- package/driver/cancel.mjs +27 -0
- package/driver/case-law-sources.mjs +3 -3
- package/driver/company-bundle.mjs +261 -0
- package/driver/compare.mjs +1 -1
- package/driver/compose-read.mjs +2 -2
- package/driver/config-inventory.mjs +2 -2
- package/driver/contract-audit.mjs +1 -1
- package/driver/contract-e3-backlog.mjs +1 -1
- package/driver/contract-vocabulary.mjs +1 -1
- package/driver/declination-call.mjs +1 -1
- package/driver/deliver-trigger.sh +3 -3
- package/driver/dev-portal.mjs +3 -1
- package/driver/digest-queue.mjs +1 -1
- package/driver/disposition-tool.mjs +1 -1
- package/driver/doc-constants.mjs +1 -1
- package/driver/drain-posture.mjs +2 -2
- package/driver/drainer-identity.mjs +1 -1
- package/driver/driver.config.mjs +44 -9
- package/driver/effective-scope.mjs +30 -1
- package/driver/effort-model.mjs +6 -6
- package/driver/engine/CONTRACT.md +2 -2
- package/driver/engine/anthropic-agent.mjs +11 -11
- package/driver/engine/jx-turn.mjs +1 -1
- package/driver/engine/mcp/gather-config.mjs +29 -4
- package/driver/engine/mcp/recording-server.mjs +73 -1
- package/driver/engine/openai-agent.mjs +1 -1
- package/driver/engine/probe.mjs +28 -4
- package/driver/enqueue-schema.mjs +23 -3
- package/driver/findings-model.mjs +2 -2
- package/driver/flag-snapshot.mjs +2 -2
- package/driver/floor-duty.mjs +2 -2
- package/driver/frame-diff-model.mjs +1 -1
- package/driver/framework-preflight.mjs +143 -0
- package/driver/gateway.mjs +9 -1
- package/driver/hit-list.mjs +1 -1
- package/driver/jx-lanes.mjs +1 -1
- package/driver/jx.mjs +1 -1
- package/driver/knockout-assess-record.mjs +1 -1
- package/driver/knockout-review-record.mjs +435 -0
- package/driver/order-probe.mjs +1 -1
- package/driver/outbox-backoff.mjs +2 -2
- package/driver/owner-use-check.mjs +2 -2
- package/driver/package.json +1 -1
- package/driver/pipeline-knockout.mjs +105 -8
- package/driver/pipeline.mjs +81 -30
- package/driver/plain-register.mjs +77 -3
- package/driver/portal-access.mjs +141 -74
- package/driver/portal-config-view.mjs +59 -70
- package/driver/portal-report.mjs +4 -4
- package/driver/portal-service.mjs +348 -141
- package/driver/portal-upstream.mjs +105 -17
- package/driver/predelivery-lint.mjs +43 -18
- package/driver/product-rows.mjs +1 -1
- package/driver/products.mjs +1 -1
- package/driver/profile-page.html +30 -5
- package/driver/profile-service.mjs +197 -26
- package/driver/profiles.mjs +48 -1
- package/driver/publish/index.mjs +31 -13
- package/driver/publish/knockout.mjs +9 -5
- package/driver/publish/office-record-links.mjs +189 -0
- package/driver/publish/parse.mjs +3 -3
- package/driver/publish/publish-inputs.mjs +26 -0
- package/driver/publish/render-knockout.mjs +42 -42
- package/driver/publish/render.mjs +29 -4
- package/driver/publish/report-data.mjs +2 -2
- package/driver/publish/seed-pool.mjs +1 -1
- package/driver/publish/templates/report.css +8 -8
- package/driver/publish/xlsx.mjs +49 -7
- package/driver/queue-watch-verdict.mjs +2 -2
- package/driver/recipe-service.mjs +1 -1
- package/driver/record-carry.mjs +1 -1
- package/driver/reference-score.mjs +1 -1
- package/driver/reference-strip-signatures.mjs +1 -1
- package/driver/register-availability.mjs +4 -3
- package/driver/register-count.mjs +3 -3
- package/driver/register-records.mjs +1 -1
- package/driver/repair-composers.mjs +1 -1
- package/driver/repairs.mjs +3 -3
- package/driver/replay-archive.mjs +1 -1
- package/driver/report-card-record.mjs +1 -1
- package/driver/result-noun-fields.mjs +5 -0
- package/driver/roster-verdict.mjs +48 -5
- package/driver/run-activity.mjs +1 -1
- package/driver/run-requirements.mjs +18 -5
- package/driver/runner.mjs +24 -15
- package/driver/search-policy.mjs +8 -8
- package/driver/senior-rights.mjs +1 -1
- package/driver/skills/prelim-search/delivery-contract.md +1 -1
- package/driver/skills/prelim-search/risk-framework-triage.md +10 -7
- package/driver/stages-knockout.mjs +72 -6
- package/driver/stages.mjs +10 -10
- package/driver/suite-census.json +238 -70
- package/driver/synthesis-record.mjs +2 -2
- package/driver/systemd/clearotron-client-mcp.service +3 -3
- package/driver/systemd/clearotron-deploy.service +2 -2
- package/driver/systemd/clearotron-mcp-face.service +1 -1
- package/driver/systemd/clearotron-portal.service +3 -3
- package/driver/systemd/clearotron-worker.service +5 -5
- package/driver/systemd/install-census.mjs +1 -1
- package/driver/systemd/render-units.mjs +9 -9
- package/driver/terminal-clamp.mjs +1 -1
- package/driver/trigger-cap.mjs +18 -2
- package/driver/unit-inventory.mjs +8 -8
- package/driver/usage-ledger.mjs +5 -3
- package/driver/verify-knockout.mjs +7 -7
- package/driver/verify.mjs +5 -5
- package/driver/whatif-memo-run.mjs +1 -1
- package/driver/whatif-queue.mjs +3 -3
- package/driver/whatif-worker.mjs +2 -2
- package/examples/README.md +1 -1
- package/examples/grants.example.json +25 -24
- package/mcp-server/CHANGELOG.md +10 -0
- package/mcp-server/http-server.mjs +3 -3
- package/mcp-server/key-socket.mjs +1 -1
- package/mcp-server/lib/audit-view.mjs +3 -3
- package/mcp-server/lib/brief.mjs +3 -3
- package/mcp-server/lib/driver.mjs +1 -1
- package/mcp-server/lib/events.mjs +1 -1
- package/mcp-server/lib/http-handler.mjs +2 -2
- package/mcp-server/lib/instructions.mjs +2 -2
- package/mcp-server/lib/knockout.mjs +1 -1
- package/mcp-server/lib/ops.mjs +6 -3
- package/mcp-server/lib/options.mjs +15 -4
- package/mcp-server/lib/plan.mjs +7 -6
- package/mcp-server/lib/runs.mjs +10 -0
- package/mcp-server/lib/whatif.mjs +5 -5
- package/mcp-server/package.json +1 -1
- package/mcp-server/packs/README.md +1 -1
- package/mcp-server/remote/client-mcp-apikey.service +1 -1
- package/mcp-server/remote/client-mcp.service +2 -2
- package/mcp-server/remote/trademark-artifacts-http.service +1 -1
- package/mcp-server/server.mjs +38 -24
- package/package.json +2 -2
- package/portal-ui/dist/assets/{index-KFAHMgdT.js → index-CWTHP0sH.js} +3471 -1901
- package/portal-ui/dist/assets/{index-1ziUJX1E.css → index-KpytsmNH.css} +79 -26
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/_shared/lane-probe.mjs +9 -3
- package/providers/jx-subclass/lookup.mjs +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/oauth-mcp-bridge/systemd/courtlistener-mcp.service +1 -1
- package/scripts/citation-drift-report.mjs +1 -1
- package/scripts/citation-line-check.mjs +2 -2
- package/scripts/drive-env-check.mjs +1 -1
- package/scripts/e2e.mjs +5 -5
- package/scripts/env-audit.mjs +13 -1
- package/scripts/headless-page.mjs +5 -5
- package/scripts/live-surface-check.mjs +26 -2
- package/scripts/mint-names-in-force.mjs +19 -5
- package/scripts/mint-reference-strip-backlog.mjs +1 -1
- package/scripts/mint-suite-census.mjs +37 -10
- package/scripts/pack-publishable.mjs +1 -1
- package/scripts/preinstall-node-check.mjs +1 -1
- package/scripts/release-await-cut.mjs +3 -3
- package/scripts/release-cut-decision.mjs +1 -1
- package/scripts/release-dist-tag.mjs +1 -1
- package/scripts/release-install-check.mjs +1 -1
- package/scripts/release-notes-lint.mjs +1 -1
- package/scripts/release-publish-guard.mjs +1 -1
- package/scripts/release-version-pr-checks.mjs +2 -2
- package/scripts/release-version.mjs +61 -5
- package/scripts/render-brand-banner.mjs +1 -1
- package/scripts/render-check.mjs +2 -2
- package/scripts/repo-writes.mjs +1 -1
- package/scripts/report-frame-check.mjs +1 -1
- package/scripts/report-screenshot.mjs +2 -2
- package/scripts/retire-bare-refs.mjs +1 -1
- package/scripts/revisit-render-check.mjs +1 -1
- package/scripts/score.mjs +1 -1
- package/scripts/strip-titles-and-attributions.mjs +389 -0
- package/scripts/strip-tracker-citations.mjs +122 -5
- package/scripts/test-run.mjs +4 -4
- package/scripts/third-party-notices.mjs +1 -1
- package/scripts/verify-publishable.mjs +1 -1
- package/shared/access-audience.mjs +2 -2
- package/shared/anon-overlay.mjs +1 -1
- package/shared/brand.mjs +15 -1
- package/shared/bundle-freshness.mjs +1 -1
- package/shared/bundle-rebuild.mjs +1 -1
- package/shared/checkout-move.mjs +2 -2
- package/shared/client-door.mjs +8 -8
- package/shared/connect-clients.mjs +7 -7
- package/shared/connector-signin-probe.mjs +1 -1
- package/shared/env-aliases.mjs +1 -1
- package/shared/env-local.mjs +5 -5
- package/shared/grants-edit.mjs +76 -0
- package/shared/install-auth.mjs +1 -1
- package/shared/listen.mjs +3 -3
- package/shared/mcp-challenge.mjs +1 -1
- package/shared/names-in-force.mjs +2 -1
- package/shared/onboarding-store.mjs +19 -2
- package/shared/reference-guard-classes.mjs +44 -2
- package/shared/register-selection.mjs +1 -1
- package/shared/scope.mjs +223 -56
- package/shared/secret-file.mjs +1 -1
- package/shared/server-units.mjs +1 -1
- package/shared/staff-domain.mjs +45 -78
- package/shared/summary-blocks.mjs +2 -2
- package/shared/systemd-failure.mjs +3 -3
- package/shared/tracked-files.mjs +1 -1
- package/shared/trigger-lane.mjs +1 -1
- package/shared/tty-style.mjs +1 -1
- package/shared/usage-block.mjs +1 -1
- package/shared/vacuous-pass.mjs +1 -1
- package/shared/verb-shim.mjs +1 -1
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
// implementation; a second copy here would be a second thing to keep true.
|
|
45
45
|
decideRecovery, failureSignature, classifyFailureReason, countRecoveryLanes, weatherCeilingFor,
|
|
46
46
|
} from "./repairs.mjs";
|
|
47
|
-
import { RunCancelled } from "./cancel.mjs"; // stop-by-user: never the failure lane below
|
|
47
|
+
import { RunCancelled, assertNotCancelledBeforePublish } from "./cancel.mjs"; // stop-by-user: never the failure lane below
|
|
48
48
|
import { loadFrameworkManifest, parseFrameworkManifest, frameworkFor, DEFAULT_FRAMEWORK } from "./framework.mjs";
|
|
49
49
|
import { KO_STAGES, KO_STEPS, KO_STEP_REGISTER_COUNT, koSteps, koPaths, kebab, knockoutPrompt, koChunks } from "./stages-knockout.mjs";
|
|
50
50
|
import { kebabCollisions, reportIdentityFor, CAPABILITY_SKIPPED_CAUSE, CAPABILITY_SKIPPED_NOTE } from "./search-policy.mjs";
|
|
@@ -55,10 +55,12 @@ import { capabilitiesFor } from "./register-capabilities.mjs";
|
|
|
55
55
|
import { registerUnavailableOffices } from "./register-unreachable.mjs";
|
|
56
56
|
import { runRecordLogPath } from "../providers/_shared/ledger-path.mjs"; // — this run's record log
|
|
57
57
|
import { validators as koValidators, validateMergedFindings, worstBand, registerSurfacedFilings, raterCaveats, SURVIVOR_BOUNDARY_RE } from "./verify-knockout.mjs";
|
|
58
|
+
import { reviewAbout, reviewEvidence, reviewEvidenceLines, applyKnockoutReview, knockoutReviewFile } from "./knockout-review-record.mjs";
|
|
58
59
|
import { publishKnockout, composeKnockoutEmail } from "./publish/knockout.mjs";
|
|
59
60
|
import { writeRunStatus, rollupStatus, atomicWrite, identitySeed } from "./progress.mjs"; // — the identity seed is shared; the stepper is not
|
|
60
61
|
import { batchMarkName } from "./mark-name.mjs";
|
|
61
62
|
import { runLog, note, outputMeta } from "./log.mjs";
|
|
63
|
+
import { defaultTerritoryState } from "./effective-scope.mjs"; // the stored-defaults reading — one producer, shared with the clearance lane
|
|
62
64
|
import { AGENT_WHATSAPP, whatsappRouting } from "./stages.mjs";
|
|
63
65
|
import { writeOutboxPacket } from "./outbox.mjs";
|
|
64
66
|
import { rollupTokens, stampTokenRollup } from "./tokens.mjs";
|
|
@@ -66,7 +68,7 @@ import { recordRunConsumption } from "./consumption-ledger.mjs";
|
|
|
66
68
|
import { writeSettleStamp } from "./settle-stamp.mjs"; // — the pool copy's own terminal state
|
|
67
69
|
import { stopReason } from "../shared/stop-reason.mjs"; //
|
|
68
70
|
import { envFrom } from "../shared/env-aliases.mjs"; // — resolves EITHER spelling; names the retired one because that is the live-writable half
|
|
69
|
-
// The scoped owner lookup a promoted register filing is owed
|
|
71
|
+
// The scoped owner lookup a promoted register filing is owed. Bounded, deduplicated
|
|
70
72
|
// per owner, and structurally unable to withhold a report.
|
|
71
73
|
import { ownersOwedACheck, runOwnerChecks } from "./owner-use-check.mjs";
|
|
72
74
|
|
|
@@ -164,6 +166,62 @@ export function readBackLadder(ctx, sidecarPath, { minted }) { // exported for
|
|
|
164
166
|
|
|
165
167
|
// ── koStage: the slim lane's stage runner — skip-if-output-valid (the stageOnce pattern), one
|
|
166
168
|
// runStage call, StageFailure on exhaustion (rate_limited rides to pipeline()'s postpone catch).
|
|
169
|
+
/**
|
|
170
|
+
* Rewrite the lines a reader meets first, and NEVER cost a client a report doing it.
|
|
171
|
+
*
|
|
172
|
+
* Every failure path returns the record it was handed. A stage that fails, times out, returns nothing,
|
|
173
|
+
* returns something that will not parse, or produces a rewrite the merged gate then refuses, all end the
|
|
174
|
+
* same way: the rated record ships, unrewritten, exactly as it shipped before this stage existed. This
|
|
175
|
+
* pass is presentation. A refusal is never a pass, and "no report" is a product failure — so the last
|
|
176
|
+
* thing added to the delivery path is the last thing that should be able to stop it.
|
|
177
|
+
*
|
|
178
|
+
* THE EMPTY CASE IS NOT A FAILURE AND IS NOT SILENT. A batch whose default-visible lines are all already
|
|
179
|
+
* plain has nothing to dispatch, and paying for a turn to be told so is waste. It is logged with the
|
|
180
|
+
* count, so "nothing was flagged" and "the pass never ran" are different rows in the journal rather than
|
|
181
|
+
* one absent one.
|
|
182
|
+
*/
|
|
183
|
+
async function knockoutReviewingPass({ ctx, run, K, merged, plan }) {
|
|
184
|
+
const about = reviewAbout(merged, plan);
|
|
185
|
+
const evidence = reviewEvidence(merged, about);
|
|
186
|
+
if (!evidence.rows.length) {
|
|
187
|
+
runLog(run.runDir, { event: "knockout-review", outcome: "nothing-flagged", flagged: 0,
|
|
188
|
+
marks: merged.marks.length, excludedMarks: about.marks.length, excludedOwners: about.owners.length });
|
|
189
|
+
return merged;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const keep = (outcome, detail) => {
|
|
193
|
+
runLog(run.runDir, { event: "knockout-review", outcome, flagged: evidence.rows.length, applied: 0,
|
|
194
|
+
delivered: "the rated record, unrewritten", ...detail });
|
|
195
|
+
note(`reviewing pass: ${outcome} — the rated record ships unrewritten`);
|
|
196
|
+
return merged;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
await koStage("knockout-review", ctx, {
|
|
201
|
+
msgCtx: { evidenceLines: reviewEvidenceLines(evidence), exclusionNote: evidence.exclusionNote },
|
|
202
|
+
});
|
|
203
|
+
} catch (e) {
|
|
204
|
+
return keep("stage-failed", { reason: String(e?.message ?? e).slice(0, 200) });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let review;
|
|
208
|
+
try { review = JSON.parse(readFileSync(knockoutReviewFile(run.runDir), "utf8")); }
|
|
209
|
+
catch (e) { return keep("artifact-unreadable", { reason: String(e?.message ?? e).slice(0, 200) }); }
|
|
210
|
+
|
|
211
|
+
const { doc, receipt } = applyKnockoutReview(merged, review);
|
|
212
|
+
// THE REWRITTEN RECORD GOES THROUGH THE SAME GATE. Not a lighter one and not none: this is the record
|
|
213
|
+
// a client receives, and the only reason it is trusted is that it passed what the rated one passed.
|
|
214
|
+
const mv = validateMergedFindings(run.runDir, doc, plan);
|
|
215
|
+
if (!mv.ok) return keep("rewrite-refused-by-the-merged-gate", { failures: mv.failures.slice(0, 5) });
|
|
216
|
+
|
|
217
|
+
atomicWrite(K.findings, JSON.stringify(doc, null, 2) + "\n");
|
|
218
|
+
runLog(run.runDir, { event: "knockout-review", outcome: "applied", flagged: evidence.rows.length,
|
|
219
|
+
applied: receipt.applied, declined: receipt.declined,
|
|
220
|
+
unresolved: receipt.unresolved.length, refused: receipt.refused.length,
|
|
221
|
+
excludedMarks: about.marks.length, excludedOwners: about.owners.length });
|
|
222
|
+
return doc;
|
|
223
|
+
}
|
|
224
|
+
|
|
167
225
|
async function koStage(name, ctx, { chunkNo = null, msgCtx = {} } = {}) {
|
|
168
226
|
// — the stage's own start, captured before ANY work: before the existsSync, before the validator
|
|
169
227
|
// read, before the message composition. states why on the spine — capture it after the await and
|
|
@@ -478,8 +536,8 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
478
536
|
//
|
|
479
537
|
// `failClass: "deterministic"` says the same thing to the OTHER reader: repairs.mjs's ladder. It
|
|
480
538
|
// was stamped when this lane had no ladder for it to reach — "so the fact travels with the throw
|
|
481
|
-
// rather than being re-guessed from prose by whichever catch the throw ends up in."
|
|
482
|
-
//
|
|
539
|
+
// rather than being re-guessed from prose by whichever catch the throw ends up in." That catch is
|
|
540
|
+
// now this lane's own, the stamp is LIVE, and it is what buys this refusal
|
|
483
541
|
// zero parks without the ladder having to read a word of the prose.
|
|
484
542
|
if (refusal) throw new StageFailure("knockout-register-count", refusal, null,
|
|
485
543
|
{ refusal: true, failClass: "deterministic" });
|
|
@@ -521,6 +579,18 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
521
579
|
marksDetailed: markRows.map((m) => ({ name: String(m.name), ...(Array.isArray(m.classes) && m.classes.length ? { classes: m.classes } : {}), ...(m.ref ? { ref: m.ref } : {}) })),
|
|
522
580
|
}, null, 2) + "\n");
|
|
523
581
|
} catch (e) { note(`instructed-scope write failed (non-fatal): ${e.message}`); }
|
|
582
|
+
// The stored defaults the engine cannot search — the clearance lane's record, on this lane too. A
|
|
583
|
+
// knockout reads the same account profile and dropped the same entries in the same silence.
|
|
584
|
+
try {
|
|
585
|
+
const dts = defaultTerritoryState(ctx.profile);
|
|
586
|
+
// two records of one fact, so each has its own try: a fault in one must not take the other
|
|
587
|
+
try { if (dts.unrecognized.length) runLog(run.runDir, { event: "default-territory-unrecognized", count: dts.unrecognized.length, entries: dts.unrecognized, lane: "knockout" }); } catch (e) { note(`default-territory-unrecognized log failed (non-fatal): ${e.message}`); }
|
|
588
|
+
writeFileSync(K.defaultTerritories, JSON.stringify({
|
|
589
|
+
profileKey: ctx.profile?.profileKey ?? null,
|
|
590
|
+
searchable: dts.kept,
|
|
591
|
+
unrecognized: dts.unrecognized,
|
|
592
|
+
}, null, 2) + "\n");
|
|
593
|
+
} catch (e) { note(`default-territories write failed (non-fatal): ${e.message}`); }
|
|
524
594
|
|
|
525
595
|
// status seed — knockout's OWN step flow (never seedRunStatus's clearance stepper). Depth 2
|
|
526
596
|
// walks one more step than a plain knockout, so the list is resolved once and frozen on ctx.
|
|
@@ -658,7 +728,7 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
658
728
|
runLog(run.runDir, { event: "knockout-register-records", provider: REGISTER_PROVIDER, executor: recExec.source,
|
|
659
729
|
marks: recDoc.marks.length, listed: listedMarks(recDoc), records: recDoc.marks.reduce((n, m) => n + m.records.length, 0) });
|
|
660
730
|
|
|
661
|
-
// ── THE OWNER LOOKUP, HERE BECAUSE HERE IS WHERE THE OWNER BECOMES KNOWN
|
|
731
|
+
// ── THE OWNER LOOKUP, HERE BECAUSE HERE IS WHERE THE OWNER BECOMES KNOWN ──────────────────────
|
|
662
732
|
//
|
|
663
733
|
// On the run that produced the issue, the owner's name was on disk 48 seconds before the sweep
|
|
664
734
|
// started and no pass ever searched it: every sweep keys on the TERM, and nothing re-swept on an
|
|
@@ -667,7 +737,7 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
667
737
|
//
|
|
668
738
|
// Bounded to promoted filings and deduplicated per owner — on the issue's own run that is ONE
|
|
669
739
|
// extra query. Never throws: a failure produces rows saying the lookup did not answer, and the
|
|
670
|
-
// run publishes (
|
|
740
|
+
// run publishes (ruling A, 2026-09-07).
|
|
671
741
|
try {
|
|
672
742
|
const owed = ownersOwedACheck(recDoc);
|
|
673
743
|
if (owed.length) {
|
|
@@ -811,7 +881,9 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
811
881
|
for (let c = 0; c < chunks.length; c++) {
|
|
812
882
|
await koStage("knockout-assess", ctx, { chunkNo: c, msgCtx: { chunkMarks: chunks[c], chunkTotal: chunks.length, framework: ctx.framework } });
|
|
813
883
|
}
|
|
814
|
-
|
|
884
|
+
// `let`, because the reviewing pass below returns a REWRITTEN record rather than editing this one:
|
|
885
|
+
// the original has to survive intact as the thing that ships if the pass cannot finish cleanly.
|
|
886
|
+
let merged = { schema_version: 1, framework: null, batch: null, marks: [] };
|
|
815
887
|
const summaries = [];
|
|
816
888
|
for (let c = 0; c < chunks.length; c++) {
|
|
817
889
|
// READ prefers the new location and falls back to the pre-relocation one, so a run whose chunks
|
|
@@ -896,9 +968,34 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
896
968
|
if (!mv.ok) throw new StageFailure("knockout-assess", `merged findings failed the lint: ${mv.failures.join("; ")}`, null);
|
|
897
969
|
runLog(run.runDir, { event: "knockout-receipts", ...mv.receipts });
|
|
898
970
|
atomicWrite(K.findings, JSON.stringify(merged, null, 2) + "\n");
|
|
971
|
+
// ── THE REVIEWING PASS, AFTER THE RECORD IS ON DISK AND BEFORE ANYTHING READS IT ─────────────────
|
|
972
|
+
//
|
|
973
|
+
// ORDER MATTERS THREE WAYS. The record is written FIRST so the stage's validator has something to
|
|
974
|
+
// join every address against — an address that names no line is refused while the seat can still
|
|
975
|
+
// send a repair turn, rather than reported as unresolved after the stage was declared good. It also
|
|
976
|
+
// means a run that dies inside the pass has already left a complete, valid, deliverable record: the
|
|
977
|
+
// rated one, unrewritten, which is exactly what shipped before this stage existed. And the rewritten
|
|
978
|
+
// record goes back through `validateMergedFindings` before it replaces that one, so nothing reaches
|
|
979
|
+
// a client that has not been through the same gate.
|
|
980
|
+
//
|
|
981
|
+
// NO NEW STEP LABEL. `stepTotal` is derived from `koSteps()`, so adding one would renumber the
|
|
982
|
+
// progress screen a client watches for a pass that takes a minute. It runs inside "Knockout
|
|
983
|
+
// assessment", which is what it is part of.
|
|
984
|
+
merged = await knockoutReviewingPass({ ctx, run, K, merged, plan });
|
|
899
985
|
try { writeFileSync(K.assessment, String(merged.batch.executiveSummary ?? "")); } catch { /* prose mirror, best-effort */ }
|
|
900
986
|
|
|
901
987
|
// 4 — publish (report + workbook + meta + index)
|
|
988
|
+
//
|
|
989
|
+
// THE LAST READ OF THE STOP FLAG. Everything above dispatches through the gateway, which reads it
|
|
990
|
+
// before a turn — so on this lane, where publication follows the final stage directly, a stop
|
|
991
|
+
// pressed during that stage was never seen and the report went out anyway.
|
|
992
|
+
assertNotCancelledBeforePublish(run.runDir, "knockout");
|
|
993
|
+
// PAST THE LAST STOPPABLE POINT, RECORDED WHERE IT BECOMES TRUE. The read above is the final one, so
|
|
994
|
+
// from this line on a stop cannot prevent delivery — and the screen must stop promising it will.
|
|
995
|
+
// Written here rather than derived on the screen from a step number: "Report & publish" is step 4 of
|
|
996
|
+
// 5 on this lane, not the last, so any arithmetic over stepN would name the wrong step and go stale
|
|
997
|
+
// the day a lane's list changes.
|
|
998
|
+
writeRunStatus(ctx, { stoppable: false });
|
|
902
999
|
koStep(ctx, "Report & publish");
|
|
903
1000
|
const overall = worstBand(ctx.framework, merged.marks);
|
|
904
1001
|
const published = await publishKnockout({
|
|
@@ -934,7 +1031,7 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
934
1031
|
conversationId: job.conversationId ?? null,
|
|
935
1032
|
subject: `Knockout trademark review — ${job.ref ?? markNames[0] ?? "batch"} (${nMarks} mark${nMarks === 1 ? "" : "s"})`,
|
|
936
1033
|
emailBodyHtml: emailHtml,
|
|
937
|
-
// THE SAME ROUTING AS THE CLEARANCE PACKET (
|
|
1034
|
+
// THE SAME ROUTING AS THE CLEARANCE PACKET (part b). This read
|
|
938
1035
|
// `AGENT_WHATSAPP[agent]`, and every user of a deployment shares one agent id, so every knockout
|
|
939
1036
|
// completion paged the operator and told the person who ordered it nothing. The clearance packet
|
|
940
1037
|
// was moved off that and this one was not, which is the half that shipped: one call site fixed,
|
package/driver/pipeline.mjs
CHANGED
|
@@ -81,7 +81,7 @@ import { profileOrdinals } from "./profile-selection.mjs"; // lever 3 — driv
|
|
|
81
81
|
// CLIENT REPORT through the script-scope coverage row, which is why it is imported rather than typed:
|
|
82
82
|
// the row used to name "Depth 5 (prelim-jx)" — a retired key on a retired ladder — as its remedy.
|
|
83
83
|
import { NATIVE_LANGUAGE_REMEDY } from "./products.mjs";
|
|
84
|
-
import { resolveTerritories } from "./effective-scope.mjs"; // the ONE territory ladder (the geography stamp included)
|
|
84
|
+
import { resolveTerritories, defaultTerritoryState } from "./effective-scope.mjs"; // the ONE territory ladder (the geography stamp included) + the stored-defaults reading
|
|
85
85
|
import { acquireSlot, releaseSlot } from "./slot-lock.mjs";
|
|
86
86
|
import { mintSupplementalEntries, withRejected } from "./engine/mcp/supplemental.mjs";
|
|
87
87
|
import { runLog, note, fileMeta, outputMeta, stageLog } from "./log.mjs"; import { armProduced, readArmSurfaces, producedNothingLine } from "./experiment-honesty.mjs"; import { correctiveReadiness, correctiveRefusalLine, correctivePassState } from "./corrective-arm.mjs";
|
|
@@ -98,7 +98,7 @@ import { buildAuditMd, parseSpineFindingBlocks } from "./publish/audit-from-spin
|
|
|
98
98
|
import { deriveRegisterPresence } from "./publish/register-presence.mjs"; // — the audit stores every live in-scope record
|
|
99
99
|
import { lastAcceptedMatterFrame } from "./matter-frame-record.mjs"; // — the frame's inferred scope, when nothing was instructed
|
|
100
100
|
import { romanizedTermsFromPlan, mintSupplementalQid } from "./register-plan.mjs"; // — the stamp the late lanes never met
|
|
101
|
-
import { slimLine, crowdLine } from "./hit-list.mjs"; // — the list the run works from; crowds ride it as a sibling array
|
|
101
|
+
import { slimLine, crowdLine } from "./hit-list.mjs"; // — the list the run works from; crowds ride it as a sibling array
|
|
102
102
|
import { mintCrossCheckDoubts, mintContradictionDoubts, stitchDoubts, applyClosure } from "./doubt-ledger.mjs"; // doubt-stitch + doubt-closure (2026-07-22)
|
|
103
103
|
// Conversion 6: the two line-form parsers are no longer on the live path — the seat sends typed
|
|
104
104
|
// rows and the driver applies THOSE. `parseClosureLines`/`parseAskClosureLines` are still exported and
|
|
@@ -178,7 +178,7 @@ import { caseLawInventory } from "./config-inventory.mjs"; // — the deployme
|
|
|
178
178
|
import { caseLawSourceRows } from "./case-law-sources.mjs"; // one author for the shape the stage is handed
|
|
179
179
|
import { writeSettleStamp } from "./settle-stamp.mjs"; // — the pool copy's own terminal state
|
|
180
180
|
import { isEntrypoint } from "../shared/is-entrypoint.mjs"; // — one entry-point test, all spellings
|
|
181
|
-
import { RunCancelled, isCancelled } from "./cancel.mjs"; // stop-by-user: a distinct class, never classified as a failure; isCancelled is the resume door's own refusal (2155)
|
|
181
|
+
import { RunCancelled, isCancelled, assertNotCancelledBeforePublish } from "./cancel.mjs"; // stop-by-user: a distinct class, never classified as a failure; isCancelled is the resume door's own refusal (2155)
|
|
182
182
|
import { BRAND } from "../shared/brand.mjs"; // — the operator name a client is told to expect, from the tenant seam
|
|
183
183
|
import { CLIENT_ACTION } from "../shared/client-failure-note.mjs"; // — the action, worded once; this surface names the mark itself
|
|
184
184
|
import { stopReason } from "../shared/stop-reason.mjs"; // — one builder, four cancel sites
|
|
@@ -699,7 +699,7 @@ function deriveHitList(ctx, band) {
|
|
|
699
699
|
// as a fallback so a provider that promotes the field is not silently ignored.
|
|
700
700
|
const lines = (band?.enumerated ?? []).map((r) =>
|
|
701
701
|
slimLine(r, r?.screen?.mark_transliteration ?? r?.mark_transliteration ?? null));
|
|
702
|
-
// ── CROWDS RIDE BESIDE THE LINES (Option A, ruled
|
|
702
|
+
// ── CROWDS RIDE BESIDE THE LINES (Option A, as ruled) ─────────────────────────────────────────
|
|
703
703
|
//
|
|
704
704
|
// A crowd is a zone that could NOT be enumerated. Without it on the list, `band_lookup` answering
|
|
705
705
|
// from the list returns nothing for a crowded zone — and nothing is indistinguishable from
|
|
@@ -1568,6 +1568,10 @@ function attachProfile(ctx, job, { write = true } = {}) {
|
|
|
1568
1568
|
// job-derived field in that subset would make it unrecomputable. The swept set is visible in the frozen
|
|
1569
1569
|
// platforms; where the extras came from is run telemetry, logged below.
|
|
1570
1570
|
ctx.profile.profileSha = profileShaOf(ctx.profile); // T9 (K3)
|
|
1571
|
+
// WHICH ORGANISATION'S GENERIC this run is filed under — the door stamps it on a Generic job, from the
|
|
1572
|
+
// verified principal. Set AFTER the hash for the reason the provenance note above gives: it is job-
|
|
1573
|
+
// derived, and `profileSha` must stay recomputable from profiles/<key>.json. Absent on a company's run.
|
|
1574
|
+
if (typeof job?.tenant === "string" && job.tenant && ctx.profile.profileKey === "generic") ctx.profile.organisation = job.tenant;
|
|
1571
1575
|
const tmp = `${sidecarPath}.tmp`;
|
|
1572
1576
|
writeFileSync(tmp, JSON.stringify(ctx.profile, null, 2) + "\n");
|
|
1573
1577
|
renameSync(tmp, sidecarPath);
|
|
@@ -2633,7 +2637,7 @@ const safeReadText = (p) => { try { return readFileSync(p, "utf8"); } catch { re
|
|
|
2633
2637
|
// a named reason, never a silent skip.
|
|
2634
2638
|
// ── — THE FLOOR DUTY, checked ────────────────────────────────────────────────────────────────
|
|
2635
2639
|
//
|
|
2636
|
-
// The floors are placement-inquiry's one uncapped obligation and nothing verified them. The
|
|
2640
|
+
// The floors are placement-inquiry's one uncapped obligation and nothing verified them. The ruling
|
|
2637
2641
|
// of 2026-08-20 made the duty machine-checkable — placed, or named by record id with a one-line ground —
|
|
2638
2642
|
// and this is the check that ruling ordered. The dictation that tells the seat to name the ids ships in
|
|
2639
2643
|
// the same commit, so a run predating it reports its WHOLE floor unmet, correctly.
|
|
@@ -4653,7 +4657,7 @@ export function buildFailurePacket({ runId, agent, job = {}, failedStage, shortR
|
|
|
4653
4657
|
// verbatim reason below this line names which.
|
|
4654
4658
|
[REFUSAL_TERMINAL_KIND]: "Nothing failed. This deployment does not serve the search that was ordered, and said so before any work was done — the reason below names what is missing and what to change. Re-running it unchanged returns the same answer.",
|
|
4655
4659
|
};
|
|
4656
|
-
// ── A CAP DEATH IS A SUBSCRIPTION OUTCOME, NOT AN OUTAGE (
|
|
4660
|
+
// ── A CAP DEATH IS A SUBSCRIPTION OUTCOME, NOT AN OUTAGE (the fourth ask) ─────────────────────────
|
|
4657
4661
|
//
|
|
4658
4662
|
// The weather lane carries BOTH an overloaded provider and a usage cap, and its one sentence
|
|
4659
4663
|
// described the first: "stayed overloaded or unreachable … Re-trigger once the provider is healthy."
|
|
@@ -5885,7 +5889,7 @@ async function enforceCorrectionsReachFindings(ctx, P, pre, resume) {
|
|
|
5885
5889
|
}
|
|
5886
5890
|
}
|
|
5887
5891
|
|
|
5888
|
-
// (T3a,
|
|
5892
|
+
// (T3a, ruling 2026-08-26 — "Deliver always, with open points printed. The refusal on a blocking
|
|
5889
5893
|
// review goes." That REVERSES T3, which had retired "delivered-with-open-questions" and deleted
|
|
5890
5894
|
// the extra this comment used to be a tombstone for. Both decisions are the owner's; this is the standing
|
|
5891
5895
|
// one. The open points are no longer a dispatch extra a seat writes up: `buildReviewerOpenPointsSection`
|
|
@@ -6034,13 +6038,20 @@ function refuteRegistryCheckExtra(ctx) {
|
|
|
6034
6038
|
* the one report where it matters most — the same defect one level in that turned "AXIS Bank filed in
|
|
6035
6039
|
* class 36" into "group Bank filed in class 36" on a report clearing AXIS.
|
|
6036
6040
|
*
|
|
6037
|
-
* WHY THE FIELDS ARE THE RECORD'S OWN KEYS AND
|
|
6038
|
-
* clearance
|
|
6039
|
-
*
|
|
6040
|
-
*
|
|
6041
|
-
*
|
|
6042
|
-
*
|
|
6043
|
-
*
|
|
6041
|
+
* WHY THE FIELDS ARE THE RECORD'S OWN KEYS, AND WHAT THAT USED TO SAY. This block could not use
|
|
6042
|
+
* `DEFAULT_VISIBLE_FIELDS.clearance`, because that list named fields no record carried — iterating it
|
|
6043
|
+
* would have opened nothing and reported a clean result over text it never read, which is the
|
|
6044
|
+
* absence-as-pass this block exists to avoid.
|
|
6045
|
+
*
|
|
6046
|
+
* That list is repaired now: every entry is a path that resolves against a delivered record, and an arm
|
|
6047
|
+
* resolves each one. The keys below are still written out rather than read from it, because this block
|
|
6048
|
+
* needs the `where` label it puts in front of each hit and the list carries paths and not labels. The
|
|
6049
|
+
* two agree on the surfaces, and the arm that resolves the list is what keeps that true.
|
|
6050
|
+
*
|
|
6051
|
+
* The old wording here also stated the defect slightly wrong, and said so about the field it was most
|
|
6052
|
+
* likely to mislead on: `thirdPartyRights` was called dead "in either casing" when the record carries
|
|
6053
|
+
* `four_answers.third_party_rights`, in fourteen files. Checking a name in one casing and reporting it
|
|
6054
|
+
* absent in both is the same error one level down from the one this paragraph is about.
|
|
6044
6055
|
*
|
|
6045
6056
|
* An absent or malformed findings.json yields no block, and the composer records the id as not built —
|
|
6046
6057
|
* so "nothing to say" and "could not look" are told apart on the dispatch receipt rather than inferred.
|
|
@@ -6249,7 +6260,7 @@ function sentinel(runDir, name, obj) {
|
|
|
6249
6260
|
}
|
|
6250
6261
|
|
|
6251
6262
|
/**
|
|
6252
|
-
* Settle every queued what-if BEFORE the run dir moves into the archive
|
|
6263
|
+
* Settle every queued what-if BEFORE the run dir moves into the archive.
|
|
6253
6264
|
*
|
|
6254
6265
|
* A queued job lives under the run directory, and the worker enumerates candidates with `liveRunDirs`,
|
|
6255
6266
|
* which skips `archive` outright. So archiving carried a pending job out of the worker's reach: not
|
|
@@ -7044,7 +7055,7 @@ export function injectZhScopeCoverage(P, runDir, note, opts = {}) { // @intern
|
|
|
7044
7055
|
* returns nothing the moment `laneRan` is true, so a `full`-configured lane that ran candidates only
|
|
7045
7056
|
* fell through every disclosure this pipeline had.
|
|
7046
7057
|
*
|
|
7047
|
-
*
|
|
7058
|
+
* RULING (2026-08-17), and it decides the shape rather than just authorising the work: "if we
|
|
7048
7059
|
* can't run deep dive on serpAPI we need to flag it" — FLAG, NOT GATE. So nothing here changes what a
|
|
7049
7060
|
* run executes or what it retrieves; it changes what the delivered document admits. `coverage-limited`
|
|
7050
7061
|
* is the state for exactly that: a disclosed limit, never a verdict clamp.
|
|
@@ -7230,7 +7241,7 @@ export function fullProseOrdinals(findings) {
|
|
|
7230
7241
|
// ADVISORY_KINDS member could never reach a client document at all. The 2026-07-16 comment described the
|
|
7231
7242
|
// behaviour it shipped with and became stale prose sitting next to code that contradicted it.
|
|
7232
7243
|
//
|
|
7233
|
-
//
|
|
7244
|
+
// Ruling 2026-08-19 (relayed), on a renewal-deadline example: it renders. That intent is the
|
|
7234
7245
|
// ruled behaviour. The tag is REQUIRED, not decoration — `advisoryLine` interpolates ADVISORY_TAG[a.kind]
|
|
7235
7246
|
// directly, so a kind that passes the filter without an entry here renders "- **undefined** …" into a
|
|
7236
7247
|
// client document. Whoever adds a fifth kind adds it in both places, and render.mjs needs the matching
|
|
@@ -7327,8 +7338,8 @@ export function buildOnlyYouSection(actions, findings, { nowMs = Date.now(), wit
|
|
|
7327
7338
|
// Terminal punctuation on the ask BEFORE the subject join. This was written to work around a cut —
|
|
7328
7339
|
// actYouConditions ended its bound line at the first '.' or ':' before whitespace, and the subject's
|
|
7329
7340
|
// own "(re:" qualified, so a period-less ask (the synthesis prompt demands one sentence but nothing
|
|
7330
|
-
// enforces terminal punctuation) delivered as a mangled "…terms (re". THAT CUT IS GONE (
|
|
7331
|
-
//
|
|
7341
|
+
// enforces terminal punctuation) delivered as a mangled "…terms (re". THAT CUT IS GONE (ruling
|
|
7342
|
+
// 2026-08-10): the conditions box now renders the ask and its subject whole. The punctuation
|
|
7332
7343
|
// stays because it was always the right output — "Instruct counsel on the joined-script forms (re: X)"
|
|
7333
7344
|
// reads as one run-on sentence, and the period is what separates the ask from the driver's own note.
|
|
7334
7345
|
// Asks with no subject are untouched.
|
|
@@ -7419,7 +7430,7 @@ export function buildOnlyYouSection(actions, findings, { nowMs = Date.now(), wit
|
|
|
7419
7430
|
|
|
7420
7431
|
// ── T3a — THE REVIEWER'S OPEN POINTS, CODE-BUILT FROM THE REVIEW ────────────────────────────────────
|
|
7421
7432
|
//
|
|
7422
|
-
//
|
|
7433
|
+
// Ruling 2026-08-26, verbatim: "Deliver always, with open points printed. The refusal on a
|
|
7423
7434
|
// blocking review goes." That REVERSES T3, which retired "delivered-with-open-questions" and
|
|
7424
7435
|
// is itself recorded as an owner-approved decision — both are his, and this is the standing one.
|
|
7425
7436
|
//
|
|
@@ -8450,7 +8461,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
8450
8461
|
// and carries the measured reason it cannot be queued (queueing it would orphan the mint the last
|
|
8451
8462
|
// flush's own pass makes). That list is censused by an arm, so this sentence cannot go stale
|
|
8452
8463
|
// silently again — it read "the ONLY path" while one mechanism had always fired its own pass, and an
|
|
8453
|
-
// invariant with an undeclared violation teaches the next reader the wrong rule
|
|
8464
|
+
// invariant with an undeclared violation teaches the next reader the wrong rule.
|
|
8454
8465
|
// Everything else: escalation/envelope/screen-gate keep
|
|
8455
8466
|
// their unit-level work but MINT durable queue items instead of firing their own opus digest pass, and
|
|
8456
8467
|
// the queue settles in ONE consolidated flush at the frame-reopen seam (pre-synthesis), plus at most
|
|
@@ -8603,6 +8614,25 @@ async function pipelineInner(job, opts = {}) {
|
|
|
8603
8614
|
geography: job.geography ?? null,
|
|
8604
8615
|
}, null, 2) + "\n");
|
|
8605
8616
|
} catch (e) { note(`instructed-scope write failed (non-fatal): ${e.message}`); }
|
|
8617
|
+
// THE STORED DEFAULTS THE ENGINE CANNOT SEARCH — recorded by the run, not only by the plan preview.
|
|
8618
|
+
//
|
|
8619
|
+
// `defaultTerritoryState` is the ONE producer of this reading (effective-scope.mjs) and it is CALLED
|
|
8620
|
+
// here rather than re-derived: the run's own territory ladder (`resolveTerritories`) passes an
|
|
8621
|
+
// unrecognised account default straight through, so before this the only surfaces that named one were
|
|
8622
|
+
// the plan response and the profile screen — both intake-side. A firm whose stored default is
|
|
8623
|
+
// misspelled got a narrower search than the configuration says, and no run artifact said so.
|
|
8624
|
+
// Written for EVERY run, empty list included: an empty array is the asserted zero, and a reader who
|
|
8625
|
+
// only ever sees this file on the runs that had a bad entry cannot tell it from a run nothing checked.
|
|
8626
|
+
try {
|
|
8627
|
+
const dts = defaultTerritoryState(ctx.profile);
|
|
8628
|
+
// two records of one fact, so each has its own try: a fault in one must not take the other
|
|
8629
|
+
try { if (dts.unrecognized.length) runLog(run.runDir, { event: "default-territory-unrecognized", count: dts.unrecognized.length, entries: dts.unrecognized }); } catch (e) { note(`default-territory-unrecognized log failed (non-fatal): ${e.message}`); }
|
|
8630
|
+
writeFileSync(P.defaultTerritories, JSON.stringify({
|
|
8631
|
+
profileKey: ctx.profile?.profileKey ?? null,
|
|
8632
|
+
searchable: dts.kept,
|
|
8633
|
+
unrecognized: dts.unrecognized,
|
|
8634
|
+
}, null, 2) + "\n");
|
|
8635
|
+
} catch (e) { note(`default-territories write failed (non-fatal): ${e.message}`); }
|
|
8606
8636
|
// Change B5 — unknown-customer state + the late-bind consumer. The intake gate sets
|
|
8607
8637
|
// job.customerUnknown when the applicant is neither stated nor forwarder-implied; the forwarding agent
|
|
8608
8638
|
// may later drop customer-bind.json into this run dir when a thread reply names the applicant (B5b).
|
|
@@ -12010,7 +12040,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
12010
12040
|
// an inventory that cannot be read hands the stage nothing, and the dictation then says nothing about
|
|
12011
12041
|
// sources rather than asserting a readiness nobody measured.
|
|
12012
12042
|
try {
|
|
12013
|
-
// COMPOSED BY THE MODULE THAT READS IT
|
|
12043
|
+
// COMPOSED BY THE MODULE THAT READS IT. This mapping used to be inline here
|
|
12014
12044
|
// and rebuilt by hand in an arm — two authors for one shape, and the arm's copy went stale the
|
|
12015
12045
|
// moment this one gained a field, while still passing. `caseLawSourceRows` is now the only place
|
|
12016
12046
|
// it is written, and it carries `checked` so an UNREADABLE credential reaches the composer as a
|
|
@@ -12472,7 +12502,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
12472
12502
|
|
|
12473
12503
|
// ── — THE TERMINAL GUARDS DELIVER AND CLAMP; THEY DO NOT WITHHOLD ───────────
|
|
12474
12504
|
//
|
|
12475
|
-
//
|
|
12505
|
+
// Ruling, 2026-08-27, verbatim intent: **reports always ship**. When a terminal guard finds
|
|
12476
12506
|
// the report incomplete at delivery, the engine sends it with the gap patched conservatively and
|
|
12477
12507
|
// the defect named in the run record. It never withholds.
|
|
12478
12508
|
//
|
|
@@ -12543,7 +12573,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
12543
12573
|
// not an oversight: if the unanswerable count is ever the large one, it wants its own issue against
|
|
12544
12574
|
// the band rather than a wider floor here.
|
|
12545
12575
|
//
|
|
12546
|
-
// NO REPAIR RUNG, deliberately and on a measurement (
|
|
12576
|
+
// NO REPAIR RUNG, deliberately and on a measurement (ruling relayed 2026-08-27). Every
|
|
12547
12577
|
// registered repair composer belongs to another stage; placement has none, and building one is a
|
|
12548
12578
|
// larger piece of work than this floor. The blast radius said it could wait: on the day this was
|
|
12549
12579
|
// written today's run discharged all 60 of its floors, and the only two runs with undischarged rows
|
|
@@ -12556,7 +12586,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
12556
12586
|
// argument was that nothing could then reach delivery unaccounted. The corrective pass is the state
|
|
12557
12587
|
// that enumeration missed: `prepareDeclinationSpec` runs again before it over a surface that can
|
|
12558
12588
|
// have grown, so it is ordered against a LARGER owed set; if that call is refused,
|
|
12559
|
-
// `rollbackCorrectivePass` restores the pre-corrective findings and — by T3b's
|
|
12589
|
+
// `rollbackCorrectivePass` restores the pre-corrective findings and — by T3b's ruling,
|
|
12560
12590
|
// deliberately — the run DELIVERS. What ships then satisfied the OLD owed set, and no acceptance
|
|
12561
12591
|
// boundary ever saw a call about the records added since, because the only call ordered against them
|
|
12562
12592
|
// was the one that was refused.
|
|
@@ -12910,7 +12940,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
12910
12940
|
try { writeVerdictSidecar(); }
|
|
12911
12941
|
catch (e) { throw new StageFailure("verdict", `verdict sidecar write failed (the single label authority): ${String(e.message).slice(0, 120)}`); }
|
|
12912
12942
|
|
|
12913
|
-
// T3a,
|
|
12943
|
+
// T3a, ruling 2026-08-26 — "Deliver always, with open points printed. The refusal on a blocking
|
|
12914
12944
|
// review goes." This REVERSES T3 (H3/H5), whose flip to fail-on-BLOCKING is itself recorded
|
|
12915
12945
|
// itself an owner-approved decision. Both are his; this is the standing one, and the
|
|
12916
12946
|
// history is kept here rather than deleted because a reader who finds only one of them will conclude
|
|
@@ -12979,7 +13009,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
12979
13009
|
}
|
|
12980
13010
|
}
|
|
12981
13011
|
if (verdict === "BLOCKING") {
|
|
12982
|
-
// ── T3a — THE RUN DELIVERS.
|
|
13012
|
+
// ── T3a — THE RUN DELIVERS. Ruling 2026-08-26, verbatim: "Deliver always, with open
|
|
12983
13013
|
// points printed. The refusal on a blocking review goes." ──────────────────────────────────
|
|
12984
13014
|
//
|
|
12985
13015
|
// What stood here threw, and its reasoning was T3: "a report the reviewer will not
|
|
@@ -13390,7 +13420,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
13390
13420
|
note(`recall: ${silent.lost.length} position(s) the digest ended as FINDINGS reached no client `
|
|
13391
13421
|
+ `surface and no step said why — ${silent.lost.map((l) => `${l.mark ?? l.uri} (${l.reason})`).join("; ")}`);
|
|
13392
13422
|
}
|
|
13393
|
-
// ── AND THE STATED CASE, WHICH IS THE ONE THAT REACHED A CLIENT
|
|
13423
|
+
// ── AND THE STATED CASE, WHICH IS THE ONE THAT REACHED A CLIENT ────────────────────────────
|
|
13394
13424
|
//
|
|
13395
13425
|
// Same inputs, same seam, same best-effort contract — a second call rather than a widened first
|
|
13396
13426
|
// one, because the sibling's population boundary is deliberate and correct. It covers a
|
|
@@ -13685,7 +13715,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
13685
13715
|
const askApplied = applyAskClosure(runAsks, acc.ask, fileTexts, { ts: askTs });
|
|
13686
13716
|
runAsks = askApplied.asks;
|
|
13687
13717
|
for (const u of askApplied.unverified) runLog(run.runDir, { event: "ask-closure-unverified", ...u });
|
|
13688
|
-
// Option A
|
|
13718
|
+
// Option A: a recall ask whose mark is not in the delivered findings
|
|
13689
13719
|
// did NOT close. Each one is a mark the run found and the client was not shown, so it is
|
|
13690
13720
|
// recorded by name rather than left to be inferred from an ask that merely stayed open.
|
|
13691
13721
|
// ✕ NOT written into findings.json here. See the sidecar's own note: a row appended after
|
|
@@ -13695,7 +13725,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
13695
13725
|
if (askApplied.carryIntoFindings?.length)
|
|
13696
13726
|
writeFileSync(driverDir(run.runDir, "marks-owed-a-finding.json"),
|
|
13697
13727
|
JSON.stringify({
|
|
13698
|
-
_provenance: "recall asks that could not close because the mark they found is absent from findings.json — owner ruling 2026-09-07,
|
|
13728
|
+
_provenance: "recall asks that could not close because the mark they found is absent from findings.json — owner ruling 2026-09-07, Option A, worded about the MARK",
|
|
13699
13729
|
_notWritten: "these are NOT appended to findings.json: a finding carries a band, a net line and a legal position, and a row minted here would carry none of them. The repair belongs where the findings are authored.",
|
|
13700
13730
|
count: askApplied.carryIntoFindings.length,
|
|
13701
13731
|
marks: askApplied.carryIntoFindings,
|
|
@@ -14647,6 +14677,13 @@ async function pipelineInner(job, opts = {}) {
|
|
|
14647
14677
|
published = JSON.parse(readFileSync(join(run.runDir, ".published"), "utf8"));
|
|
14648
14678
|
note(`publish skip — already published → ${published.url}`);
|
|
14649
14679
|
} else {
|
|
14680
|
+
// THE LAST READ OF THE STOP FLAG, and it sits HERE rather than above the branch: a run that has
|
|
14681
|
+
// already published has delivered, and refusing at that point would record a delivered run as
|
|
14682
|
+
// cancelled — the same defect pointed the other way.
|
|
14683
|
+
assertNotCancelledBeforePublish(run.runDir, "clearance");
|
|
14684
|
+
// Same fact, same moment, same words as the knockout lane: past this line a stop cannot prevent
|
|
14685
|
+
// delivery, and the screen is told so rather than working it out.
|
|
14686
|
+
writeRunStatus(ctx, { stoppable: false });
|
|
14650
14687
|
published = await publishReport({
|
|
14651
14688
|
runId: `${run.slug}-${run.date}-${run.codename}`, codename: run.codename,
|
|
14652
14689
|
reportMd: P.report, auditMd: P.audit, findingsJson: P.findings, poolRoot: config.poolRoot, poolUrl: config.poolUrl,
|
|
@@ -14659,6 +14696,20 @@ async function pipelineInner(job, opts = {}) {
|
|
|
14659
14696
|
});
|
|
14660
14697
|
sentinel(run.runDir, ".published", { runId: published.runId, url: published.url, counts: published.counts,
|
|
14661
14698
|
clientGate: published.clientGate }); // machine-QC record (observability) — decides nothing
|
|
14699
|
+
// WHAT THE TWO GATES EACH SAID, in the run's own log, under names that say whose they are.
|
|
14700
|
+
// `.published` carries `clientGate.released` beside `counts.gateViolations`, which are the verdicts
|
|
14701
|
+
// of DIFFERENT gates: the first is the client-export refusal (evaluateClientGate), the second is the
|
|
14702
|
+
// audit workbook's own build check (validateAudit), which is advisory by design and decides nothing
|
|
14703
|
+
// about the release. Read as one gate they contradict each other — released, with a list of
|
|
14704
|
+
// violations beside it — and a delivered run shipped looking exactly like that. Named separately
|
|
14705
|
+
// here, with the advisory COUNT and the absences the gate did not close on, so the log a person
|
|
14706
|
+
// reads after a run answers "what did the checks say" without opening the sentinel and knowing
|
|
14707
|
+
// which field belongs to which gate.
|
|
14708
|
+
runLog(run.runDir, { event: "publish-gates",
|
|
14709
|
+
clientExport: published.clientGate?.released === false ? "closed" : "released",
|
|
14710
|
+
clientExportReasons: published.clientGate?.reasons ?? [],
|
|
14711
|
+
releasedDespiteAbsentInputs: (published.clientGate?.notClosing ?? []).map((n) => n.input),
|
|
14712
|
+
auditWorkbookAdvisory: published.counts?.gateViolations?.length ?? 0 });
|
|
14662
14713
|
note(`published → ${published.url}`);
|
|
14663
14714
|
}
|
|
14664
14715
|
// Lifecycle honesty (charter P1 §4): publish is CODE, not a stage() call, so nothing ever advanced the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
2
|
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
3
3
|
//
|
|
4
|
-
// THE TWO-REGISTER RULE, AS THE REVIEWER READS IT
|
|
4
|
+
// THE TWO-REGISTER RULE, AS THE REVIEWER READS IT.
|
|
5
5
|
//
|
|
6
6
|
// The report goes to a lawyer who layers advice on top, and that lawyer's client reads the same page.
|
|
7
7
|
// The band, the summary, the basis line and the one-liners are the whole product for the second reader,
|
|
@@ -127,8 +127,82 @@ export function plainRegisterFlags(text, about = {}) {
|
|
|
127
127
|
/**
|
|
128
128
|
* The fields a reader meets before opening anything. Named here rather than at each call site so the
|
|
129
129
|
* two products answer to one list — the knockout and the clearance drifted apart once already.
|
|
130
|
+
*
|
|
131
|
+
* EVERY ENTRY IS A PATH INTO A DELIVERED RECORD, and an arm resolves each one against the runs under
|
|
132
|
+
* `demo/`. That is the whole repair. The clearance half named eight fields of which six did not exist
|
|
133
|
+
* in any casing, and the reason nobody noticed is that a list of names is only ever read BY a person:
|
|
134
|
+
* `thirdPartyRights` beside a record carrying `four_answers.third_party_rights` reads as correct, and a
|
|
135
|
+
* loop over it opens nothing and reports a clean result over text it never read. A path either resolves
|
|
136
|
+
* or the arm says so.
|
|
137
|
+
*
|
|
138
|
+
* THE PATHS ARE THE SURFACES THE LIVE CHECK ALREADY READS. `plainRegisterExtra` in pipeline.mjs walks
|
|
139
|
+
* the clearance record's own keys and had a comment explaining that it could not use this list. The list
|
|
140
|
+
* is now what that walk reads, plus the four answers, which render on the report as prose and were
|
|
141
|
+
* missing from both. One definition, and the walk is the thing that made it checkable.
|
|
142
|
+
*
|
|
143
|
+
* `[]` MARKS A LIST and `*` an object's own values: `findings[].net` is one sentence per finding, and
|
|
144
|
+
* `four_answers.*.read` one per answer — that register is keyed by answer name and is not an array.
|
|
145
|
+
*
|
|
146
|
+
* WHAT CAME OFF, AND WHY EACH. `oneLiner` and `freedomToOperate` exist in no record in either casing and
|
|
147
|
+
* name no surface anybody could point at — they are gone rather than renamed, because inventing a target
|
|
148
|
+
* for them would be a guess in the one place a guess reads as a fact. `ownRights` resolves to
|
|
149
|
+
* `findings[].own_rights.source`, which is a record URI and not prose: a citation has no register, and
|
|
150
|
+
* running a plain-words check over one would flag the profession's vocabulary inside a machine
|
|
151
|
+
* identifier. `batchOpener` was in the KNOCKOUT half and is dead in both casings there.
|
|
152
|
+
*
|
|
153
|
+
* KNOWN INCOMPLETE, DELIBERATELY. `marks[].registerReads[].read` is walked as knockout-visible prose by
|
|
154
|
+
* `knockoutVisibleProse` and is not on the knockout list here. Adding it would widen what the reviewing
|
|
155
|
+
* pass rewrites on a delivered report, which is a change to what a client receives and not this repair's
|
|
156
|
+
* to make. The acceptance here is that every entry names something real, not that the list is complete;
|
|
157
|
+
* the completeness question is recorded on the issue rather than settled in passing.
|
|
130
158
|
*/
|
|
131
159
|
export const DEFAULT_VISIBLE_FIELDS = Object.freeze({
|
|
132
|
-
knockout: [
|
|
133
|
-
|
|
160
|
+
knockout: [
|
|
161
|
+
"batch.executiveSummary", "batch.standardCaveats[]",
|
|
162
|
+
"marks[].basis", "marks[].factors[]", "marks[].counterFactors[]", "marks[].mitigation",
|
|
163
|
+
"marks[].purpleNotes[]", "marks[].findings[].net",
|
|
164
|
+
],
|
|
165
|
+
clearance: [
|
|
166
|
+
"mark_assessment.distinctiveness", "mark_assessment.connotation",
|
|
167
|
+
"four_answers.*.read", "findings[].net", "coverage[].note", "actions[].text",
|
|
168
|
+
],
|
|
134
169
|
});
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Every string a path in `DEFAULT_VISIBLE_FIELDS` reaches in one delivered record.
|
|
173
|
+
*
|
|
174
|
+
* THIS IS WHAT MAKES THE LIST CHECKABLE, and its absence is why six dead names sat there for months. A
|
|
175
|
+
* list of bare names can only be read by a person; a path can be resolved, so an arm can say which
|
|
176
|
+
* entries open nothing.
|
|
177
|
+
*
|
|
178
|
+
* `[]` walks a list, `*` walks an object's own values — `four_answers` is keyed by answer name rather
|
|
179
|
+
* than being an array, and a path that assumed a list there resolved to nothing while looking right.
|
|
180
|
+
*
|
|
181
|
+
* TWO FIELDS ARRIVE AS EITHER A STRING OR AN OBJECT, and this reads both because the code that renders
|
|
182
|
+
* them does. A reviewer's note is `p?.text ?? p` in `knockoutVisibleProse`; an assessment field is
|
|
183
|
+
* `typeof v === "string" ? v : v?.read` where the structured form carries typed rows beside the prose.
|
|
184
|
+
* Resolving only the string form would have reported both as dead on a record that carries them, which
|
|
185
|
+
* is the same absence-dressed-as-a-fact this repair exists to remove — measured, on the delivered runs.
|
|
186
|
+
*
|
|
187
|
+
* A missing key yields NOTHING rather than throwing. An absent optional field is a fact about that
|
|
188
|
+
* record, and the arm decides what an absence means across several of them.
|
|
189
|
+
*/
|
|
190
|
+
export function resolveVisiblePath(record, path) {
|
|
191
|
+
let nodes = [record];
|
|
192
|
+
for (const seg of String(path).split(".")) {
|
|
193
|
+
const list = seg.endsWith("[]");
|
|
194
|
+
const key = list ? seg.slice(0, -2) : seg;
|
|
195
|
+
const next = [];
|
|
196
|
+
for (const n of nodes) {
|
|
197
|
+
if (n == null || typeof n !== "object") continue;
|
|
198
|
+
if (key === "*") { next.push(...Object.values(n)); continue; }
|
|
199
|
+
const v = key ? n[key] : n;
|
|
200
|
+
if (v == null) continue;
|
|
201
|
+
if (list && Array.isArray(v)) next.push(...v); else next.push(v);
|
|
202
|
+
}
|
|
203
|
+
nodes = next;
|
|
204
|
+
}
|
|
205
|
+
return nodes
|
|
206
|
+
.map((n) => (typeof n === "string" ? n : (typeof n?.text === "string" ? n.text : n?.read)))
|
|
207
|
+
.filter((s) => typeof s === "string" && s.trim());
|
|
208
|
+
}
|