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
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
3
|
+
//
|
|
4
|
+
// record_knockout_review — the knockout lane's REVIEWING pass, as a typed return path.
|
|
5
|
+
//
|
|
6
|
+
// The screening product had two stages and no reviewing pass, so a plain-language flag on a line a
|
|
7
|
+
// client reads was a note to whoever was fixing the run and nothing rewrote it. The clearance product's
|
|
8
|
+
// equivalent rides `narrative-refutation`; this is the same rule reaching the same reader on the other
|
|
9
|
+
// product, through the smallest stage that can carry it.
|
|
10
|
+
//
|
|
11
|
+
// WHAT THE SEAT SENDS, AND WHY IT IS AN ADDRESS AND NOT A QUOTE.
|
|
12
|
+
//
|
|
13
|
+
// Each rewrite names the field it replaces by a typed address the DRIVER measured and handed over —
|
|
14
|
+
// {field, mark?, index?, ordinal?} — and carries the replacement prose. It does not quote the sentence
|
|
15
|
+
// it is replacing. A quote has to be matched back against the record, and the matcher is then the thing
|
|
16
|
+
// that decides where a rewrite lands: on the clearance lane a correction the cycle cannot bind to an
|
|
17
|
+
// entity is declined whatever its merit, and prose-matching is how a rewrite comes to land on the wrong
|
|
18
|
+
// mark when two names differ only in case. The address is the join key, and it is refused by name when
|
|
19
|
+
// it does not resolve.
|
|
20
|
+
//
|
|
21
|
+
// EVERY KEY'S MERGE RULE, STATED BEFORE THE CODE.
|
|
22
|
+
//
|
|
23
|
+
// `rewrites[]` — merged onto the stored call BY ADDRESS. A repair turn that re-sends one address
|
|
24
|
+
// replaces that rewrite whole; addresses it omits survive. This is the same rule the frame and assess
|
|
25
|
+
// transports state for a mark row, for the same reason: a seat correcting one line should not have to
|
|
26
|
+
// re-send the other twenty, and a partial payload that DELETED the rest is the failure that rule exists
|
|
27
|
+
// to stop.
|
|
28
|
+
//
|
|
29
|
+
// `declined[]` — merged the same way, by address. A seat that read a flag and judged the word to be the
|
|
30
|
+
// subject rather than the profession's shorthand says so here. This is not decoration: the pass is
|
|
31
|
+
// handed evidence and never a verdict, so "I looked and it is fine" and "I did not look" must be
|
|
32
|
+
// different states in the artifact, or the residue cannot be read afterwards.
|
|
33
|
+
//
|
|
34
|
+
// AND THE PASS DECIDES NOTHING.
|
|
35
|
+
//
|
|
36
|
+
// No band, no finding, no count and no record moves. The only thing a rewrite may change is the prose
|
|
37
|
+
// of a default-visible line. The acceptor enforces that by construction: the address grammar cannot
|
|
38
|
+
// express a band, an ordinal reassignment or a new finding, so there is no payload that reaches them.
|
|
39
|
+
import { writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
40
|
+
import { join, dirname, basename } from "node:path";
|
|
41
|
+
import { driverDir, DRIVER_DIR } from "../shared/driver-dir.mjs";
|
|
42
|
+
import { captureCall, stampVerdict } from "./call-capture.mjs";
|
|
43
|
+
import { refuseUndeclared as refuseUndeclaredShared, lastAccepted, acceptedEnvelope } from "./preserve-merge.mjs";
|
|
44
|
+
import { knockoutVisibleProse } from "./predelivery-lint.mjs"; // the walk is the authority on what a reader sees
|
|
45
|
+
import { plainRegisterFlags } from "./plain-register.mjs"; // the pinned rule, one copy
|
|
46
|
+
|
|
47
|
+
const SCHEMA_VERSION = 1;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Path → the keys that path declares, in `refuseUndeclared`'s own shape.
|
|
51
|
+
*
|
|
52
|
+
* The top level ("") is deliberately permissive by that helper's rule — an envelope field nobody reads
|
|
53
|
+
* must never kill a stage. The care is inside the typed sub-objects: a rewrite whose replacement prose
|
|
54
|
+
* arrived under a key this tool does not declare would be accepted at the top level and then dropped,
|
|
55
|
+
* and the artifact would say the seat rewrote a line it did not.
|
|
56
|
+
*/
|
|
57
|
+
const DECLARED = {
|
|
58
|
+
"": ["schema_version", "rewrites", "declined"],
|
|
59
|
+
rewrites: ["at", "text", "why"],
|
|
60
|
+
"rewrites.at": ["field", "mark", "index", "ordinal"],
|
|
61
|
+
declined: ["at", "why"],
|
|
62
|
+
"declined.at": ["field", "mark", "index", "ordinal"],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The addressable fields, and the shape each address takes.
|
|
67
|
+
*
|
|
68
|
+
* THE SET IS WHAT A CLIENT SEES FIRST, WHICH IS NARROWER THAN THE WALK — in two different directions,
|
|
69
|
+
* and each has to be said or the next reader repairs the wrong one.
|
|
70
|
+
*
|
|
71
|
+
* NARROWER BY WHO WROTE IT. The engine appends two of the standing caveats itself. They are in the walk
|
|
72
|
+
* because an over-long engine caveat is a real defect for whoever edits the code that emits it, and they
|
|
73
|
+
* are not addressable because a rewrite there breaks a delivery-time match or appends a second copy of
|
|
74
|
+
* the note, silently either way.
|
|
75
|
+
*
|
|
76
|
+
* NARROWER BY WHAT A READER MEETS. `marks[].registerReads[].read` is in the walk and is NOT in
|
|
77
|
+
* `DEFAULT_VISIBLE_FIELDS.knockout`. The walk is the authority on what the internal lint should READ;
|
|
78
|
+
* that list is the authority on what a client meets before opening anything, and this pass is scoped to
|
|
79
|
+
* the second — its own instruction to the seat says "a line a reader meets before opening anything", and
|
|
80
|
+
* the ruling that authorised it says the lines a client reads. So a field that is visible to the lint and
|
|
81
|
+
* not on that list is outside the pass, and the pass reaches eight.
|
|
82
|
+
*
|
|
83
|
+
* THAT THE TWO LISTS DISAGREE AT ALL IS A SEPARATE DEFECT, and it is filed as one rather than repaired
|
|
84
|
+
* here. Reconciling them is a change to what the product calls default-visible; making the pass ACT on
|
|
85
|
+
* the disagreement — which is what including `registerReads` did — would settle that question as a side
|
|
86
|
+
* effect of a rewrite pass, on delivered prose, with nothing recording that it had been decided.
|
|
87
|
+
*
|
|
88
|
+
* An arm compares the SETS rather than the counts, and names each difference. Both lists happened to
|
|
89
|
+
* hold nine entries while being different nines, which is exactly how a reconciliation reads as correct
|
|
90
|
+
* when it is not.
|
|
91
|
+
*/
|
|
92
|
+
export const ADDRESSABLE = Object.freeze({
|
|
93
|
+
"batch.executiveSummary": Object.freeze([]),
|
|
94
|
+
"batch.standardCaveats": Object.freeze(["index"]),
|
|
95
|
+
basis: Object.freeze(["mark"]),
|
|
96
|
+
mitigation: Object.freeze(["mark"]),
|
|
97
|
+
factors: Object.freeze(["mark", "index"]),
|
|
98
|
+
counterFactors: Object.freeze(["mark", "index"]),
|
|
99
|
+
purpleNotes: Object.freeze(["mark", "index"]),
|
|
100
|
+
"findings.net": Object.freeze(["mark", "ordinal"]),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/** One file per call, refusals included, beside the other transports' capture directories. */
|
|
104
|
+
export function knockoutReviewCallPaths(runDir) {
|
|
105
|
+
const dir = driverDir(runDir, "knockout-review-calls");
|
|
106
|
+
return { dir, accepted: join(dir, "accepted.json"), refusals: join(dir, "refusals.jsonl") };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The artifact this transport writes, AT THE RUN ROOT.
|
|
111
|
+
*
|
|
112
|
+
* NOT UNDER `_driver/`, and the lane learned that the expensive way. `_driver/` is the driver's own
|
|
113
|
+
* measurements and is behind a deny hook; a stage whose `out` lands there has a seat that will be
|
|
114
|
+
* refused on Write and on Bash, and a corrective ladder that exhausts against a dispatch ordering a
|
|
115
|
+
* write nothing permits. The assess chunk was relocated out of it for exactly that reason. This file is
|
|
116
|
+
* a MODEL output — the seat's rewrites — so the run root is where it belongs by the same rule, and an
|
|
117
|
+
* arm sweeps both stage tables to keep it there.
|
|
118
|
+
*
|
|
119
|
+
* The CALL captures stay under `_driver/`: those are the driver's record of what arrived, not a seat's
|
|
120
|
+
* output, and nothing dispatches against them.
|
|
121
|
+
*/
|
|
122
|
+
export function knockoutReviewFile(runDir) {
|
|
123
|
+
return join(String(runDir ?? ""), "knockout-review.json");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The address as one string, for joining and for saying which address was refused.
|
|
128
|
+
*
|
|
129
|
+
* ONE SPELLING. The merge joins on it, the refusal names it and the applier looks it up, so a second
|
|
130
|
+
* way of writing the same address is a rewrite that merges against itself and applies to nothing.
|
|
131
|
+
*
|
|
132
|
+
* THE SEPARATOR IS A NUL, not a space, and it is named rather than inlined. A mark name may contain a
|
|
133
|
+
* space — "OPEN COUNTRY" is an ordinary mark — so a space-joined key lets two different addresses
|
|
134
|
+
* collide into one string, and the second rewrite would silently replace the first in the merge. A NUL
|
|
135
|
+
* cannot appear in a field name or a mark name. It is also invisible in a diff, which is why it is a
|
|
136
|
+
* named constant: the property is in the name, where a reader can see it.
|
|
137
|
+
*/
|
|
138
|
+
const ADDRESS_SEP = "\u0000"; // named: a literal control character in the source is invisible
|
|
139
|
+
|
|
140
|
+
export function addressKey(at) {
|
|
141
|
+
const a = at ?? {};
|
|
142
|
+
return [a.field, a.mark ?? "", a.index ?? "", a.ordinal ?? ""].join(ADDRESS_SEP);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Is this a well-formed address for a field the pass may reach? The reason, or null. */
|
|
146
|
+
export function addressFault(at) {
|
|
147
|
+
if (!at || typeof at !== "object" || Array.isArray(at)) return "an address must be an object";
|
|
148
|
+
const field = at.field;
|
|
149
|
+
if (typeof field !== "string" || !Object.hasOwn(ADDRESSABLE, field))
|
|
150
|
+
return `"${String(field)}" is not an addressable field — one of ${Object.keys(ADDRESSABLE).join(", ")}`;
|
|
151
|
+
const need = ADDRESSABLE[field];
|
|
152
|
+
for (const k of need) {
|
|
153
|
+
if (at[k] === undefined || at[k] === null) return `address for "${field}" needs ${k}`;
|
|
154
|
+
if (k === "mark" && (typeof at.mark !== "string" || !at.mark.trim())) return `address for "${field}": mark must be a name`;
|
|
155
|
+
if ((k === "index" || k === "ordinal") && !Number.isInteger(at[k])) return `address for "${field}": ${k} must be an integer`;
|
|
156
|
+
}
|
|
157
|
+
for (const k of Object.keys(at)) {
|
|
158
|
+
if (k === "field" || need.includes(k)) continue;
|
|
159
|
+
return `address for "${field}" does not take "${k}"`;
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** SHAPE ONLY. Whether an address resolves against THIS run's record is the validator's join, not this. */
|
|
165
|
+
export function acceptKnockoutReview(call) {
|
|
166
|
+
const rewrites = call?.rewrites, declined = call?.declined;
|
|
167
|
+
if (rewrites !== undefined && !Array.isArray(rewrites)) return { ok: false, reason: "rewrites must be an array" };
|
|
168
|
+
if (declined !== undefined && !Array.isArray(declined)) return { ok: false, reason: "declined must be an array" };
|
|
169
|
+
const seen = new Set();
|
|
170
|
+
for (const r of rewrites ?? []) {
|
|
171
|
+
const fault = addressFault(r?.at);
|
|
172
|
+
if (fault) return { ok: false, reason: `a rewrite's ${fault}` };
|
|
173
|
+
if (typeof r?.text !== "string" || !r.text.trim())
|
|
174
|
+
return { ok: false, reason: `the rewrite for "${r?.at?.field}" carries no replacement text` };
|
|
175
|
+
for (const k of Object.keys(r)) if (!["at", "text", "why"].includes(k)) return { ok: false, reason: `a rewrite does not take "${k}"` };
|
|
176
|
+
const key = addressKey(r.at);
|
|
177
|
+
if (seen.has(key)) return { ok: false, reason: `two rewrites address the same line ("${r.at.field}") — send one` };
|
|
178
|
+
seen.add(key);
|
|
179
|
+
}
|
|
180
|
+
for (const d of declined ?? []) {
|
|
181
|
+
const fault = addressFault(d?.at);
|
|
182
|
+
if (fault) return { ok: false, reason: `a declined row's ${fault}` };
|
|
183
|
+
if (typeof d?.why !== "string" || !d.why.trim()) return { ok: false, reason: "a declined row must say why the line stands" };
|
|
184
|
+
for (const k of Object.keys(d)) if (!["at", "why"].includes(k)) return { ok: false, reason: `a declined row does not take "${k}"` };
|
|
185
|
+
}
|
|
186
|
+
// An empty call and a stage that never ran leave the same artifact behind, and they are different
|
|
187
|
+
// findings: one says the seat read the lines and had nothing to change, the other says nothing looked.
|
|
188
|
+
if (!(rewrites ?? []).length && !(declined ?? []).length)
|
|
189
|
+
return { ok: false, reason: "send at least one rewrite or one declined row — an empty call cannot be told from a stage that never ran" };
|
|
190
|
+
return { ok: true, model: { schema_version: SCHEMA_VERSION, rewrites: rewrites ?? [], declined: declined ?? [] } };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Merge a repair turn onto what was already accepted, BY ADDRESS. See the header for the per-key rule. */
|
|
194
|
+
export function mergeKnockoutReviewCall(was, now) {
|
|
195
|
+
const merge = (key) => {
|
|
196
|
+
const byAddr = new Map();
|
|
197
|
+
for (const row of was?.[key] ?? []) byAddr.set(addressKey(row?.at), row);
|
|
198
|
+
for (const row of now?.[key] ?? []) byAddr.set(addressKey(row?.at), row);
|
|
199
|
+
return [...byAddr.values()];
|
|
200
|
+
};
|
|
201
|
+
if (!was) return { ...now };
|
|
202
|
+
return { ...was, ...now, rewrites: merge("rewrites"), declined: merge("declined") };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* What the driver MEASURED, as the evidence this stage is dispatched with.
|
|
207
|
+
*
|
|
208
|
+
* THE DRIVER MEASURES, THE SEAT JUDGES — the same split the clearance block states. `plainRegisterFlags`
|
|
209
|
+
* is deterministic and says which line carries a word of the profession's and how long a sentence ran.
|
|
210
|
+
* Whether the sentence is actually wrong, and what replaces it, is the seat's call. The ruling behind
|
|
211
|
+
* this rejects a word list AS THE MECHANISM, so the seat is handed evidence and never a verdict: half
|
|
212
|
+
* these words are ordinary English and several are plausible marks.
|
|
213
|
+
*
|
|
214
|
+
* BLIND TO THE RUN'S OWN NOUNS, and the empty case SAYS SO. `about` blanks every mark and owner before
|
|
215
|
+
* the text is read, because a check that flagged the mark being cleared would put its noise on the one
|
|
216
|
+
* report where it matters most. When the run named no mark to exclude, the seat is told that outright —
|
|
217
|
+
* an absolute reassurance is false in exactly the state where a flag IS the mark, and telling a seat
|
|
218
|
+
* nothing is worse than telling it the truth about what was not done.
|
|
219
|
+
*
|
|
220
|
+
* ENGINE-OWNED LINES ARE NOT OFFERED. They are in the walk and out of the pass's reach; see the applier.
|
|
221
|
+
*/
|
|
222
|
+
export function reviewEvidence(merged, about = {}) {
|
|
223
|
+
const rows = [];
|
|
224
|
+
for (const { where, text, at } of knockoutVisibleProse(merged)) {
|
|
225
|
+
// OFFERED ONLY WHAT CAN BE REWRITTEN. The walk is wider than this pass in both directions — see
|
|
226
|
+
// ADDRESSABLE — and handing a seat a line it cannot address wastes the turn and teaches it that a
|
|
227
|
+
// refused address is normal.
|
|
228
|
+
if (at?.engineOwned || !Object.hasOwn(ADDRESSABLE, at?.field)) continue;
|
|
229
|
+
const flags = plainRegisterFlags(text, about);
|
|
230
|
+
if (!flags.length) continue;
|
|
231
|
+
rows.push({ at, where, says: flags.map((f) => f.say) });
|
|
232
|
+
}
|
|
233
|
+
const marks = (about.marks ?? []).filter(Boolean), owners = (about.owners ?? []).filter(Boolean);
|
|
234
|
+
const ownerNote = owners.length ? ` The ${owners.length} owner name(s) from the record were also removed.` : "";
|
|
235
|
+
const exclusionNote = marks.length
|
|
236
|
+
? `The ${marks.length} mark(s) this run is about were removed before reading, so none of these is a hit inside a name being screened.${ownerNote}`
|
|
237
|
+
: `THIS RUN NAMED NO MARK TO EXCLUDE, so the names being screened were NOT removed before reading — a flag below may BE one of them. Check each against the batch before rewriting it.${ownerNote}`;
|
|
238
|
+
return { rows, exclusionNote };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** The addresses the driver offered this run, for the seat to quote back. One line each. */
|
|
242
|
+
export function reviewEvidenceLines({ rows }) {
|
|
243
|
+
return rows.map(({ at, where, says }) => `- ${where} — address ${JSON.stringify(at)} — ${says.join(" ")}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* The `about` a knockout run is blind to: every name in the batch, and every owner on the record.
|
|
248
|
+
*
|
|
249
|
+
* TWO SOURCES, AND ONLY ONE OF THEM PROTECTS THE NAME BEING SCREENED. Owners come from the RECORD and
|
|
250
|
+
* marks from the PLAN, so keying the reassurance on the union lets a record carrying thirteen owners
|
|
251
|
+
* fire it over a batch that named no mark — telling the seat no flag is a screened name, over a flag
|
|
252
|
+
* that is exactly that. The clearance side measured this; the two are counted separately here for it.
|
|
253
|
+
*/
|
|
254
|
+
export function reviewAbout(merged, plan) {
|
|
255
|
+
const marks = [
|
|
256
|
+
...(plan?.marks ?? []).map((m) => (typeof m === "string" ? m : m?.name)),
|
|
257
|
+
...(merged?.marks ?? []).map((m) => m?.name),
|
|
258
|
+
].filter((x) => typeof x === "string" && x.trim());
|
|
259
|
+
const owners = (merged?.marks ?? [])
|
|
260
|
+
.flatMap((m) => [...(m?.findings ?? []).map((f) => f?.owner), ...(m?.registerReads ?? []).map((r) => r?.owner)])
|
|
261
|
+
.filter((x) => typeof x === "string" && x.trim());
|
|
262
|
+
return { marks: [...new Set(marks)], owners: [...new Set(owners)] };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The stage's `validate` — shape, and the JOIN against the record this run actually holds.
|
|
267
|
+
*
|
|
268
|
+
* WHY IT LIVES HERE AND NOT IN `koValidators`. `verify-knockout.mjs` is where the lane's other
|
|
269
|
+
* validators sit, and putting this one there would close an import cycle: this module reads the walk
|
|
270
|
+
* from `predelivery-lint.mjs`, which reads `isEngineAppendedCaveat` from `verify-knockout.mjs`. ESM
|
|
271
|
+
* would survive it and the hazard would be a const read during module init, years from now, by someone
|
|
272
|
+
* who did not know the cycle was there. The stage def imports its validator from here instead.
|
|
273
|
+
*
|
|
274
|
+
* THE JOIN IS THE HALF THAT MATTERS. Shape alone accepts a rewrite aimed at a mark this run does not
|
|
275
|
+
* carry, and the applier would then report it unresolved AFTER the stage had been declared good — a
|
|
276
|
+
* seat's whole turn wasted with nothing telling it why. Refusing here puts the reason in front of the
|
|
277
|
+
* seat while it can still send a repair turn.
|
|
278
|
+
*/
|
|
279
|
+
export function validateKnockoutReviewFile(file, text) {
|
|
280
|
+
let doc;
|
|
281
|
+
try { doc = JSON.parse(text); } catch (e) { return { ok: false, reason: `not valid JSON: ${e.message}` }; }
|
|
282
|
+
const shape = acceptKnockoutReview(doc);
|
|
283
|
+
if (!shape.ok) return shape;
|
|
284
|
+
|
|
285
|
+
// The run dir, from the artifact's own path rather than a fixed depth — the assumption that bit the
|
|
286
|
+
// assess validator when its chunk file moved out of `_driver/`.
|
|
287
|
+
const dir = dirname(file);
|
|
288
|
+
const runDir = basename(dir) === DRIVER_DIR ? dirname(dir) : dir;
|
|
289
|
+
const findingsFile = join(runDir, "knockout-findings.json");
|
|
290
|
+
if (!existsSync(findingsFile)) {
|
|
291
|
+
// A COULD-NOT-LOOK, SAID AS ONE. Passing here would let every address through unchecked and read on
|
|
292
|
+
// the receipt exactly like a run whose addresses all resolved.
|
|
293
|
+
return { ok: false, reason: `the merged record is not on disk at ${findingsFile}, so no address could be checked` };
|
|
294
|
+
}
|
|
295
|
+
let merged;
|
|
296
|
+
try { merged = JSON.parse(readFileSync(findingsFile, "utf8")); }
|
|
297
|
+
catch (e) { return { ok: false, reason: `the merged record will not parse, so no address could be checked: ${e.message}` }; }
|
|
298
|
+
|
|
299
|
+
const known = new Set(knockoutVisibleProse(merged).map((v) => addressKey(v.at)));
|
|
300
|
+
const owned = new Set(knockoutVisibleProse(merged).filter((v) => v.at?.engineOwned).map((v) => addressKey(v.at)));
|
|
301
|
+
for (const r of doc.rewrites ?? []) {
|
|
302
|
+
const key = addressKey(r.at);
|
|
303
|
+
if (owned.has(key)) return { ok: false, reason: `the rewrite for ${r.at.field} names a caveat the engine wrote, not a seat — it is not offered and cannot be changed` };
|
|
304
|
+
if (!known.has(key)) return { ok: false, reason: `the rewrite for ${r.at.field}${r.at.mark ? ` on "${r.at.mark}"` : ""} names no line on this record` };
|
|
305
|
+
}
|
|
306
|
+
for (const d of doc.declined ?? []) {
|
|
307
|
+
if (!known.has(addressKey(d.at))) return { ok: false, reason: `the declined row for ${d.at.field}${d.at.mark ? ` on "${d.at.mark}"` : ""} names no line on this record` };
|
|
308
|
+
}
|
|
309
|
+
return { ok: true };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Apply accepted rewrites to the merged record, and say exactly what happened to each one.
|
|
314
|
+
*
|
|
315
|
+
* WHAT THIS MAY CHANGE, AND WHAT IT MAY NOT. Prose on a default-visible line, and nothing else. It
|
|
316
|
+
* never adds or removes a mark, a finding or a caveat, never touches a band, an ordinal, evidence or a
|
|
317
|
+
* count, and never lengthens or shortens an array. An address that does not resolve against THIS record
|
|
318
|
+
* is reported as unresolved and changes nothing — a rewrite aimed at a mark the run does not carry is a
|
|
319
|
+
* defect in the pass, and applying it by nearest match is how it would become a defect in the report.
|
|
320
|
+
*
|
|
321
|
+
* A CLONE, NOT AN IN-PLACE EDIT. The caller keeps the original to fall back to, and the failure path
|
|
322
|
+
* below depends on there being one: this pass is the last thing that should be able to cost a client a
|
|
323
|
+
* report, so anything it cannot do cleanly it does not do at all.
|
|
324
|
+
*
|
|
325
|
+
* `engineOwned` IS REFUSED HERE TOO, not only left out of the dispatch. A caveat the engine appended is
|
|
326
|
+
* matched at delivery by `SURVIVOR_BOUNDARY_RE`; a rewrite that landed on it would either break that
|
|
327
|
+
* match or append a second copy of the note, and both are silent. The dispatch never offers those
|
|
328
|
+
* lines, so a rewrite naming one did not come from the evidence it was handed.
|
|
329
|
+
*/
|
|
330
|
+
export function applyKnockoutReview(merged, review) {
|
|
331
|
+
const doc = JSON.parse(JSON.stringify(merged ?? {}));
|
|
332
|
+
// THE WALK IS RE-DERIVED FROM THIS RECORD, never passed in. An index is only in range for the record
|
|
333
|
+
// it was measured on, and a caller holding a walk from before the merge would write into a slot that
|
|
334
|
+
// has moved — silently, because the address still resolves. Deriving it here makes the two the same
|
|
335
|
+
// record by construction.
|
|
336
|
+
const byKey = new Map();
|
|
337
|
+
for (const v of knockoutVisibleProse(doc)) byKey.set(addressKey(v.at), v);
|
|
338
|
+
|
|
339
|
+
const applied = [], unresolved = [], refused = [];
|
|
340
|
+
for (const r of review?.rewrites ?? []) {
|
|
341
|
+
const key = addressKey(r.at);
|
|
342
|
+
const seen = byKey.get(key);
|
|
343
|
+
if (!seen) { unresolved.push({ at: r.at, why: "no such line on this record" }); continue; }
|
|
344
|
+
if (seen.at?.engineOwned) { refused.push({ at: r.at, why: "the engine wrote this caveat, not a seat" }); continue; }
|
|
345
|
+
const at = r.at, text = String(r.text);
|
|
346
|
+
// THE WALK IS THE BOUNDS CHECK. `seen` came from this same record, so a resolved address names a
|
|
347
|
+
// field that exists and an index that is in range. The two lookups below cannot fail while that
|
|
348
|
+
// holds — they are here because if they ever DO fire, the walk and the record disagree about the
|
|
349
|
+
// shape of the same document, and writing through that disagreement is how a rewrite lands on the
|
|
350
|
+
// wrong mark. They report; they never guess a nearest match.
|
|
351
|
+
const mark = at.mark == null ? null : (doc.marks ?? []).find((m) => String(m?.name ?? "") === at.mark);
|
|
352
|
+
if (at.mark != null && !mark) { unresolved.push({ at, why: "the walk and the record disagree: no such mark" }); continue; }
|
|
353
|
+
switch (at.field) {
|
|
354
|
+
case "batch.executiveSummary": doc.batch.executiveSummary = text; break;
|
|
355
|
+
case "batch.standardCaveats": doc.batch.standardCaveats[at.index] = text; break;
|
|
356
|
+
case "basis": mark.basis = text; break;
|
|
357
|
+
case "mitigation": mark.mitigation = text; break;
|
|
358
|
+
case "factors": mark.factors[at.index] = text; break;
|
|
359
|
+
case "counterFactors": mark.counterFactors[at.index] = text; break;
|
|
360
|
+
case "purpleNotes": {
|
|
361
|
+
const note = mark.purpleNotes[at.index];
|
|
362
|
+
if (note && typeof note === "object") note.text = text; else mark.purpleNotes[at.index] = text;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
case "registerReads": mark.registerReads[at.index].read = text; break;
|
|
366
|
+
case "findings.net": {
|
|
367
|
+
// JOINED ON THE ORDINAL, never on position — the merged gate re-ranks and renumbers on the band,
|
|
368
|
+
// so the index that addressed this row upstream addresses a different row after normalisation.
|
|
369
|
+
const f = (mark.findings ?? []).find((x) => x?.ordinal === at.ordinal);
|
|
370
|
+
if (!f) { unresolved.push({ at, why: "the walk and the record disagree: no finding with that ordinal" }); continue; }
|
|
371
|
+
f.net = text;
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
default: refused.push({ at, why: "not an addressable field" }); continue;
|
|
375
|
+
}
|
|
376
|
+
applied.push({ at, chars: text.length });
|
|
377
|
+
}
|
|
378
|
+
return { doc, receipt: { applied: applied.length, unresolved, refused, declined: (review?.declined ?? []).length } };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function recordKnockoutReview(runDir, received, { now = () => new Date().toISOString() } = {}) {
|
|
382
|
+
const dir = String(runDir ?? "");
|
|
383
|
+
const paths = knockoutReviewCallPaths(dir);
|
|
384
|
+
const nameFor = (seq) => join(paths.dir, `call-${String(seq).padStart(3, "0")}.json`);
|
|
385
|
+
const cap = captureCall({ nameFor, params: received, now });
|
|
386
|
+
const captureFailed = cap.failed;
|
|
387
|
+
const closeCapture = (v) => { stampVerdict(cap.path, v); return cap.path; };
|
|
388
|
+
|
|
389
|
+
const journal = (reason) => {
|
|
390
|
+
try { writeFileSync(paths.refusals, JSON.stringify({ at: now(), reason }) + "\n", { flag: "a" }); }
|
|
391
|
+
catch { /* the refusal still stands and is still returned */ }
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const undeclared = refuseUndeclaredShared(received, DECLARED, "knockoutreview");
|
|
395
|
+
if (undeclared) {
|
|
396
|
+
journal(undeclared);
|
|
397
|
+
return { written: null, refused: undeclared, captured: closeCapture({ ok: false, refused: undeclared }), capture_failed: captureFailed };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// THE READER IS PASSED. `lastAccepted` throws without it rather than answering "nothing stored yet" —
|
|
401
|
+
// the failure knockout-frame shipped, where every repair turn merged onto an empty base and was
|
|
402
|
+
// refused for a field the seat had already sent.
|
|
403
|
+
const call = mergeKnockoutReviewCall(lastAccepted(paths.accepted, readFileSync), received);
|
|
404
|
+
const verdict = acceptKnockoutReview(call);
|
|
405
|
+
if (!verdict.ok) {
|
|
406
|
+
journal(verdict.reason);
|
|
407
|
+
return { written: null, refused: verdict.reason, captured: closeCapture({ ok: false, refused: verdict.reason }), capture_failed: captureFailed };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const file = knockoutReviewFile(dir);
|
|
411
|
+
try {
|
|
412
|
+
writeFileSync(file, JSON.stringify(verdict.model, null, 2) + "\n");
|
|
413
|
+
writeFileSync(paths.accepted, acceptedEnvelope(call, now()));
|
|
414
|
+
} catch (e) {
|
|
415
|
+
return { written: null, refused: null, write_failed: String(e?.message ?? e).slice(0, 200),
|
|
416
|
+
captured: closeCapture({ ok: false, write_failed: true }), capture_failed: captureFailed };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return {
|
|
420
|
+
written: file,
|
|
421
|
+
refused: null,
|
|
422
|
+
rewrites: verdict.model.rewrites.length,
|
|
423
|
+
declined: verdict.model.declined.length,
|
|
424
|
+
captured: closeCapture({ ok: true }),
|
|
425
|
+
capture_failed: captureFailed,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** The refusals journalled for this run, newest last. The accessor a TOOL_WRITTEN_ARTIFACTS row carries. */
|
|
430
|
+
export function knockoutReviewRefusalsFor(runDir) {
|
|
431
|
+
try {
|
|
432
|
+
return readFileSync(knockoutReviewCallPaths(String(runDir ?? "")).refusals, "utf8")
|
|
433
|
+
.split("\n").filter(Boolean).map((l) => { try { return JSON.parse(l); } catch { return { reason: l }; } });
|
|
434
|
+
} catch { return []; }
|
|
435
|
+
}
|
package/driver/order-probe.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// problem; two runs that disagree because the list was sorted differently are a different problem with a
|
|
11
11
|
// different fix, and today we cannot tell them apart.
|
|
12
12
|
//
|
|
13
|
-
// THE CONSTRAINT, and it is the whole design (
|
|
13
|
+
// THE CONSTRAINT, and it is the whole design (ruling, 2026-08-01): the seam must be PROVABLY INERT
|
|
14
14
|
// by default. Production ordering is byte-identical unless someone sets an explicit environment variable
|
|
15
15
|
// to a positive integer, and a test pins that. An order probe that could fire by accident during the
|
|
16
16
|
// round it exists to make readable would be worse than not having one.
|
|
@@ -269,11 +269,11 @@ export function settleProgress(agent, now = Date.now()) {
|
|
|
269
269
|
// them. Measured: 25 of 25 delivered runs carried it, 0 of 29 failed, parked or cancelled. A
|
|
270
270
|
// predicate that is correct by accident is one careless write away from being wrong.
|
|
271
271
|
//
|
|
272
|
-
// **
|
|
272
|
+
// **Ruling, 2026-08-22, verbatim: "clean up the failed runs. they owe the client nothing."** That
|
|
273
273
|
// produced a `state === "delivered"` filter here, and the failure packets already in the outbox were
|
|
274
274
|
// disposed of on the box.
|
|
275
275
|
//
|
|
276
|
-
// **
|
|
276
|
+
// **Ruling, 2026-08-24, SUPERSEDING THIS SWEEP'S HALF OF IT: failed runs' notification packets get
|
|
277
277
|
// the same re-drop cover as delivered ones.**
|
|
278
278
|
//
|
|
279
279
|
// BOTH ARE KEPT BECAUSE BOTH ARE STILL TRUE, and reading them as a reversal is the mistake to avoid. A
|
|
@@ -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
|
// owner-use-check.mjs — the scoped owner lookup a Knockout search owes a promoted register filing
|
|
4
|
-
|
|
4
|
+
//.
|
|
5
5
|
//
|
|
6
6
|
// THE DEFECT. When the register pass named the owner of an identical live registration, nothing ever
|
|
7
7
|
// searched that owner. The assessment inferred what the owner sells from the owner's NAME and its class
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// `**Use-check source:**` label and the honest `perplexity_research — no result`. What differs is where
|
|
32
32
|
// the proof lives, and it differs because this lane has a receipt the other one does not.
|
|
33
33
|
//
|
|
34
|
-
// ──
|
|
34
|
+
// ── RULING, 2026-09-07: THE ABSENCE OF A CITE NEVER REFUSES DELIVERY ──────────────────────────
|
|
35
35
|
//
|
|
36
36
|
// Option A. The report delivers with an honest "no result" on any row the lookup could not answer; the
|
|
37
37
|
// cite is enforced in that it is always PRESENT and always the driver's, never in the sense that a
|
package/driver/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "clearotron-driver",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.3.0-beta.1",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"description": "Deterministic driver for the trademark clearance workflow: orchestration in code (fan-out, fan-in barrier, gating, retries); the model does judgment leaves only, through a reasoning CLI spawned per stage.",
|
|
8
8
|
"engines": {
|