@clossys/writer 0.4.0 → 0.5.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/README.md +592 -2
- package/dist/approval-bypass.d.ts +120 -0
- package/dist/approval-bypass.d.ts.map +1 -0
- package/dist/approval-bypass.js +2318 -0
- package/dist/approval-bypass.js.map +1 -0
- package/dist/approval.d.ts +62 -0
- package/dist/approval.d.ts.map +1 -0
- package/dist/approval.js +421 -0
- package/dist/approval.js.map +1 -0
- package/dist/approve-cli.d.ts +82 -0
- package/dist/approve-cli.d.ts.map +1 -0
- package/dist/approve-cli.js +430 -0
- package/dist/approve-cli.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +48 -13
- package/dist/cli.js.map +1 -1
- package/dist/field-options.d.ts +59 -0
- package/dist/field-options.d.ts.map +1 -0
- package/dist/field-options.js +109 -0
- package/dist/field-options.js.map +1 -0
- package/dist/front-door-browser.d.ts +5 -0
- package/dist/front-door-browser.d.ts.map +1 -0
- package/dist/front-door-browser.js +30 -0
- package/dist/front-door-browser.js.map +1 -0
- package/dist/front-door-core.d.ts +45 -0
- package/dist/front-door-core.d.ts.map +1 -0
- package/dist/front-door-core.js +224 -0
- package/dist/front-door-core.js.map +1 -0
- package/dist/front-door.check.d.ts +2 -0
- package/dist/front-door.check.d.ts.map +1 -0
- package/dist/front-door.check.js +8 -0
- package/dist/front-door.check.js.map +1 -0
- package/dist/front-door.d.ts +46 -0
- package/dist/front-door.d.ts.map +1 -0
- package/dist/front-door.js +61 -0
- package/dist/front-door.js.map +1 -0
- package/dist/generated/advisor-plan-contract.generated.d.ts +3 -0
- package/dist/generated/advisor-plan-contract.generated.d.ts.map +1 -0
- package/dist/generated/advisor-plan-contract.generated.js +430 -0
- package/dist/generated/advisor-plan-contract.generated.js.map +1 -0
- package/dist/generated/contract-schema.generated.d.ts +97 -0
- package/dist/generated/contract-schema.generated.d.ts.map +1 -0
- package/dist/generated/contract-schema.generated.js +496 -0
- package/dist/generated/contract-schema.generated.js.map +1 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -1
- package/dist/messaging-kit.d.ts +113 -0
- package/dist/messaging-kit.d.ts.map +1 -0
- package/dist/messaging-kit.js +121 -0
- package/dist/messaging-kit.js.map +1 -0
- package/dist/passage.d.ts +2 -1
- package/dist/passage.d.ts.map +1 -1
- package/dist/passage.js +2 -1
- package/dist/passage.js.map +1 -1
- package/dist/plan-authority.d.ts +73 -0
- package/dist/plan-authority.d.ts.map +1 -0
- package/dist/plan-authority.js +126 -0
- package/dist/plan-authority.js.map +1 -0
- package/dist/plan-contract.d.ts +16 -0
- package/dist/plan-contract.d.ts.map +1 -0
- package/dist/plan-contract.js +33 -0
- package/dist/plan-contract.js.map +1 -0
- package/dist/plan-digest.d.ts +23 -0
- package/dist/plan-digest.d.ts.map +1 -0
- package/dist/plan-digest.js +82 -0
- package/dist/plan-digest.js.map +1 -0
- package/dist/plan-rules.d.ts +54 -0
- package/dist/plan-rules.d.ts.map +1 -0
- package/dist/plan-rules.js +145 -0
- package/dist/plan-rules.js.map +1 -0
- package/dist/resolve.d.ts +40 -6
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +144 -18
- package/dist/resolve.js.map +1 -1
- package/dist/scan.d.ts +32 -0
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +67 -6
- package/dist/scan.js.map +1 -1
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +209 -5
- package/dist/schema.js.map +1 -1
- package/dist/site-identity.d.ts +88 -0
- package/dist/site-identity.d.ts.map +1 -0
- package/dist/site-identity.js +82 -0
- package/dist/site-identity.js.map +1 -0
- package/dist/types.d.ts +65 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -7
- package/dist/types.js.map +1 -1
- package/package.json +11 -6
- package/skill/SKILL.md +2 -2
- package/src/approval-bypass.ts +2263 -0
- package/src/approval.ts +491 -0
- package/src/approve-cli.ts +474 -0
- package/src/cli.ts +46 -13
- package/src/field-options.ts +134 -0
- package/src/front-door-browser.ts +31 -0
- package/src/front-door-core.ts +265 -0
- package/src/front-door.check.ts +10 -0
- package/src/front-door.ts +65 -0
- package/src/generated/advisor-plan-contract.generated.ts +430 -0
- package/src/generated/contract-schema.generated.ts +520 -0
- package/src/index.ts +97 -1
- package/src/messaging-kit.ts +195 -0
- package/src/passage.ts +2 -1
- package/src/plan-authority.ts +160 -0
- package/src/plan-contract.ts +45 -0
- package/src/plan-digest.ts +81 -0
- package/src/plan-rules.ts +179 -0
- package/src/resolve.ts +185 -19
- package/src/scan.ts +97 -6
- package/src/schema.ts +223 -5
- package/src/site-identity.ts +125 -0
- package/src/types.ts +72 -7
- package/templates/front-door.en.json +423 -0
package/dist/approval.js
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval-record helpers shared by the schema, `writer-check approve` and
|
|
3
|
+
* `writer-check approval-state`.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { computeCopyFingerprint } from "./fingerprint.js";
|
|
8
|
+
import { checkApprovalBypass, scanApprovalBypass } from "./approval-bypass.js";
|
|
9
|
+
import { parseCopyRegistry } from "./schema.js";
|
|
10
|
+
/**
|
|
11
|
+
* One delegate scope item: a dot-separated entry-id namespace with no
|
|
12
|
+
* wildcard, e.g. `"site"` or `"site.home"`. Unlike `CopyEntryId`, a single
|
|
13
|
+
* segment is allowed, because a namespace may be a top-level one.
|
|
14
|
+
*/
|
|
15
|
+
export const COPY_DELEGATE_SCOPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*(\.[a-z0-9]+(-[a-z0-9]+)*)*$/;
|
|
16
|
+
/** True when `value` is a well-formed delegate scope item. */
|
|
17
|
+
export function isDelegateScopeItem(value) {
|
|
18
|
+
return typeof value === "string" && COPY_DELEGATE_SCOPE_RE.test(value);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* True when `entryId` falls inside one of `scope`'s namespaces: it equals an
|
|
22
|
+
* item, or starts with `item + "."`. `"site.home"` covers `"site.home"` and
|
|
23
|
+
* `"site.home.title"`, never `"site.homepage.title"`.
|
|
24
|
+
*/
|
|
25
|
+
export function isEntryInDelegateScope(entryId, scope) {
|
|
26
|
+
return scope.some((item) => entryId === item || entryId.startsWith(`${item}.`));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* True when `entry`'s recorded `textFingerprint` no longer matches its
|
|
30
|
+
* current `text` — the record was made for wording that has since changed.
|
|
31
|
+
* Exported so `resolve.ts` and `assessCopyApprovals` share one predicate
|
|
32
|
+
* rather than two independently-drifting copies of the same comparison.
|
|
33
|
+
*/
|
|
34
|
+
export function isApprovalStale(entry) {
|
|
35
|
+
const approval = entry.approval;
|
|
36
|
+
if (!approval)
|
|
37
|
+
return false;
|
|
38
|
+
return computeCopyFingerprint(entry.text) !== approval.textFingerprint;
|
|
39
|
+
}
|
|
40
|
+
/** True when `approval.expiresAt` is set and has passed as of `now`. */
|
|
41
|
+
export function isApprovalExpired(approval, now) {
|
|
42
|
+
return approval.expiresAt !== undefined && now.getTime() >= Date.parse(approval.expiresAt);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Reports the current approval state of every `approved` entry in
|
|
46
|
+
* `registry` — draft and retired entries have nothing to approve and are
|
|
47
|
+
* ignored. An `approved` entry with no record at all is a warning
|
|
48
|
+
* (`approval-record-missing`): the pre-record state every registry started
|
|
49
|
+
* in, not yet a defect. A record that no longer matches its entry's text
|
|
50
|
+
* (`approval-stale`) or has passed its `expiresAt` (`approval-expired`) is
|
|
51
|
+
* an error — the checks are mutually exclusive, staleness taking priority
|
|
52
|
+
* since an expired-but-also-stale record's fingerprint mismatch is the more
|
|
53
|
+
* fundamental problem. A current delegate record still awaiting owner
|
|
54
|
+
* sign-off (`pendingOwnerReview: true`) is a warning, not an error: the
|
|
55
|
+
* entry is legitimately approved for now, just not yet durably so.
|
|
56
|
+
*/
|
|
57
|
+
export function assessCopyApprovals(registry, now) {
|
|
58
|
+
const findings = [];
|
|
59
|
+
for (const entry of registry.entries) {
|
|
60
|
+
if (entry.status !== "approved")
|
|
61
|
+
continue;
|
|
62
|
+
const approval = entry.approval;
|
|
63
|
+
if (!approval) {
|
|
64
|
+
findings.push({
|
|
65
|
+
rule: "approval-record-missing",
|
|
66
|
+
severity: "warning",
|
|
67
|
+
entryId: entry.id,
|
|
68
|
+
message: `Entry "${entry.id}" is approved but has no approval record.`,
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (isApprovalStale(entry)) {
|
|
73
|
+
findings.push({
|
|
74
|
+
rule: "approval-stale",
|
|
75
|
+
severity: "error",
|
|
76
|
+
entryId: entry.id,
|
|
77
|
+
message: `Entry "${entry.id}"'s approval record no longer matches its current text.`,
|
|
78
|
+
});
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (isApprovalExpired(approval, now)) {
|
|
82
|
+
findings.push({
|
|
83
|
+
rule: "approval-expired",
|
|
84
|
+
severity: "error",
|
|
85
|
+
entryId: entry.id,
|
|
86
|
+
message: `Entry "${entry.id}"'s approval expired at ${approval.expiresAt}.`,
|
|
87
|
+
});
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (approval.approvedBy === "delegate" && approval.pendingOwnerReview === true) {
|
|
91
|
+
findings.push({
|
|
92
|
+
rule: "approval-pending-owner-review",
|
|
93
|
+
severity: "warning",
|
|
94
|
+
entryId: entry.id,
|
|
95
|
+
message: `Entry "${entry.id}" was approved by a delegate and is still awaiting owner review.`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return findings;
|
|
100
|
+
}
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// `writer-check approval-state` — combines TWO independent checks into one
|
|
103
|
+
// three-state verdict: `assessCopyApprovals` above (is the REGISTRY's own
|
|
104
|
+
// approval bookkeeping current — no stale or expired record, no delegate
|
|
105
|
+
// record stuck pending review) and `scanApprovalBypass`/`checkApprovalBypass`
|
|
106
|
+
// (`./approval-bypass.ts`) (does CONSUMER SOURCE actually route copy through
|
|
107
|
+
// that bookkeeping, or around it). Either half can fail independently: a
|
|
108
|
+
// registry can be perfectly current while a component still hard-codes
|
|
109
|
+
// `status: "approved"`, and a codebase can route every read through
|
|
110
|
+
// `createCopyResolver` while the registry itself carries a record that has
|
|
111
|
+
// gone stale. This command is the one place both are reported together,
|
|
112
|
+
// under one exit code.
|
|
113
|
+
//
|
|
114
|
+
// Not exported from `index.ts` (like `mainApproveCommand` in
|
|
115
|
+
// `approve-cli.ts`) — this is CLI surface, dispatched from `cli.ts`'s
|
|
116
|
+
// bottom-of-file `argv[0]` branch, not library surface.
|
|
117
|
+
// ============================================================================
|
|
118
|
+
export const APPROVAL_STATE_USAGE = `Usage: writer-check approval-state <registry-file> <scan-dir> [options]
|
|
119
|
+
|
|
120
|
+
registry-file Path to a CopyRegistry JSON file. Required.
|
|
121
|
+
scan-dir Directory to scan for approval-bypass patterns in consumer source. Required.
|
|
122
|
+
|
|
123
|
+
Reports the current approval state of a copy registry from two angles: is
|
|
124
|
+
every approved entry's own record current (see assessCopyApprovals in
|
|
125
|
+
approval.ts — a stale or expired record is an error; a missing record or a
|
|
126
|
+
delegate record still pending owner review is a warning), and does consumer
|
|
127
|
+
source in scan-dir actually route copy through the registry's approval
|
|
128
|
+
lifecycle rather than around it (see scanApprovalBypass/checkApprovalBypass
|
|
129
|
+
in approval-bypass.ts — approval-set-in-code and copy-read-without-resolver
|
|
130
|
+
are errors; an unchecked registry load or reference is neither a pass nor a
|
|
131
|
+
finding, just an incomplete picture).
|
|
132
|
+
|
|
133
|
+
Options:
|
|
134
|
+
--help Print this message and exit 0.
|
|
135
|
+
--extensions <ext>[,<ext>...] File extension(s) to scan, including the leading dot (for example .mjs). Repeatable, and each occurrence may itself be a comma-separated list; values union across both forms. When omitted, default is .ts, .tsx, .js, .jsx. Every value must include the leading dot; a flag that resolves to zero extensions is a usage error.
|
|
136
|
+
--format <json> When "json", prints exactly one machine-readable object ({ verdict, findings, unchecked, counts }) to stdout instead of the human-readable report. Any other value is a usage error.
|
|
137
|
+
--now <ISO> ISO 8601 UTC timestamp (YYYY-MM-DDTHH:MM:SSZ, optionally with milliseconds) to evaluate staleness/expiry against. Defaults to the current time.
|
|
138
|
+
|
|
139
|
+
Exit codes: 0 = satisfied (every approved entry's record is current, no
|
|
140
|
+
approval-bypass finding, nothing left unchecked, and at least one file was
|
|
141
|
+
scanned), 1 = violated (at least one error-severity registry finding —
|
|
142
|
+
approval-stale or approval-expired — or at least one approval-bypass finding
|
|
143
|
+
— approval-set-in-code or copy-read-without-resolver), 2 = could not run or
|
|
144
|
+
indeterminate (bad arguments; the registry file is missing/unreadable/not
|
|
145
|
+
valid JSON/fails validateCopyRegistryShape; scan-dir is not a directory; the
|
|
146
|
+
scan itself could not run; or — with zero error-severity finding — at least
|
|
147
|
+
one unchecked registry load/reference, at least one file that failed to
|
|
148
|
+
parse, or zero files scanned). Warnings never change the exit code.
|
|
149
|
+
`;
|
|
150
|
+
/** Bad arguments only — mirrors `approve-cli.ts`'s `ApproveInputError`. */
|
|
151
|
+
class ApprovalStateInputError extends Error {
|
|
152
|
+
}
|
|
153
|
+
const APPROVAL_STATE_EXTENSION_RE = /^\.[a-zA-Z0-9]+$/;
|
|
154
|
+
/** Same shape schema.ts's own (unexported) `ISO_UTC_TIMESTAMP_RE` accepts. */
|
|
155
|
+
const APPROVAL_STATE_NOW_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
|
|
156
|
+
/**
|
|
157
|
+
* Parses `--extensions` with the identical rules `cli.ts`'s
|
|
158
|
+
* `parseAddressabilityArgs` applies to its own `--extensions` flag —
|
|
159
|
+
* repeatable, comma-separated, leading dot required, an empty resolved set
|
|
160
|
+
* is a usage error rather than a silent fallback to the default. Duplicated
|
|
161
|
+
* rather than imported from `cli.ts`, since `cli.ts` imports this file's
|
|
162
|
+
* `mainApprovalStateCommand` — importing back would create a real
|
|
163
|
+
* value-level cycle between the two, unlike the `schema.ts` cycle this
|
|
164
|
+
* file's `parseCopyRegistry` import already tolerates (see this section's
|
|
165
|
+
* top doc comment).
|
|
166
|
+
*/
|
|
167
|
+
function parseApprovalStateArgs(argv) {
|
|
168
|
+
let registryFile;
|
|
169
|
+
let scanDir;
|
|
170
|
+
const extensions = [];
|
|
171
|
+
let sawExtensions = false;
|
|
172
|
+
let format = "text";
|
|
173
|
+
let now;
|
|
174
|
+
let help = false;
|
|
175
|
+
for (let i = 0; i < argv.length; i++) {
|
|
176
|
+
const arg = argv[i];
|
|
177
|
+
if (arg === "--help" || arg === "-h") {
|
|
178
|
+
help = true;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (arg === "--extensions") {
|
|
182
|
+
sawExtensions = true;
|
|
183
|
+
const value = argv[++i];
|
|
184
|
+
if (value === undefined || value.startsWith("-")) {
|
|
185
|
+
throw new ApprovalStateInputError("--extensions requires an extension such as .mjs");
|
|
186
|
+
}
|
|
187
|
+
const tokens = value.split(",");
|
|
188
|
+
if (tokens.length === 0 || tokens.some((token) => token.trim().length === 0)) {
|
|
189
|
+
throw new ApprovalStateInputError(`--extensions requires at least one non-empty extension such as .mjs, got ${JSON.stringify(value)}`);
|
|
190
|
+
}
|
|
191
|
+
for (const rawToken of tokens) {
|
|
192
|
+
const token = rawToken.trim().toLowerCase();
|
|
193
|
+
if (!token.startsWith(".")) {
|
|
194
|
+
throw new ApprovalStateInputError(`--extensions values must include the leading dot, got ${JSON.stringify(token)}`);
|
|
195
|
+
}
|
|
196
|
+
if (!APPROVAL_STATE_EXTENSION_RE.test(token)) {
|
|
197
|
+
throw new ApprovalStateInputError(`invalid --extensions value ${JSON.stringify(token)}`);
|
|
198
|
+
}
|
|
199
|
+
extensions.push(token);
|
|
200
|
+
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (arg === "--format") {
|
|
204
|
+
const value = argv[++i];
|
|
205
|
+
if (value !== "json") {
|
|
206
|
+
throw new ApprovalStateInputError(`--format only accepts "json", got ${JSON.stringify(value)}`);
|
|
207
|
+
}
|
|
208
|
+
format = "json";
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (arg === "--now") {
|
|
212
|
+
const value = argv[++i];
|
|
213
|
+
if (value === undefined || value.startsWith("-")) {
|
|
214
|
+
throw new ApprovalStateInputError("--now requires an ISO 8601 UTC timestamp");
|
|
215
|
+
}
|
|
216
|
+
now = value;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (arg.startsWith("-")) {
|
|
220
|
+
throw new ApprovalStateInputError(`unknown flag "${arg}"`);
|
|
221
|
+
}
|
|
222
|
+
if (registryFile === undefined) {
|
|
223
|
+
registryFile = arg;
|
|
224
|
+
}
|
|
225
|
+
else if (scanDir === undefined) {
|
|
226
|
+
scanDir = arg;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
throw new ApprovalStateInputError(`unexpected extra argument "${arg}"`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { registryFile, scanDir, extensions: sawExtensions ? extensions : undefined, format, now, help };
|
|
233
|
+
}
|
|
234
|
+
function parseApprovalStateNow(raw) {
|
|
235
|
+
if (raw === undefined)
|
|
236
|
+
return new Date();
|
|
237
|
+
if (!APPROVAL_STATE_NOW_RE.test(raw) || Number.isNaN(Date.parse(raw))) {
|
|
238
|
+
throw new ApprovalStateInputError(`--now must be an ISO 8601 UTC timestamp (e.g. 2026-01-01T00:00:00Z), got ${JSON.stringify(raw)}`);
|
|
239
|
+
}
|
|
240
|
+
return new Date(raw);
|
|
241
|
+
}
|
|
242
|
+
function errorMessage(error) {
|
|
243
|
+
return error instanceof Error ? error.message : String(error);
|
|
244
|
+
}
|
|
245
|
+
function combineUnchecked(scan, bypass) {
|
|
246
|
+
const fromParseFailures = scan.parseFailures.map((p) => ({
|
|
247
|
+
file: p.file,
|
|
248
|
+
line: 0,
|
|
249
|
+
kind: "parse-failure",
|
|
250
|
+
detail: p.detail,
|
|
251
|
+
}));
|
|
252
|
+
return [...bypass.unchecked, ...fromParseFailures];
|
|
253
|
+
}
|
|
254
|
+
function computeApprovalStateVerdict(registryFindings, bypass) {
|
|
255
|
+
const hasRegistryError = registryFindings.some((f) => f.severity === "error");
|
|
256
|
+
if (hasRegistryError || bypass.verdict === "violated")
|
|
257
|
+
return "violated";
|
|
258
|
+
if (bypass.verdict === "indeterminate")
|
|
259
|
+
return "indeterminate";
|
|
260
|
+
return "satisfied";
|
|
261
|
+
}
|
|
262
|
+
function approvalStateExitCode(verdict) {
|
|
263
|
+
return verdict === "violated" ? 1 : verdict === "indeterminate" ? 2 : 0;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Emitted when this command cannot even reach the point of scanning (a bad
|
|
267
|
+
* registry file, an unusable scan-dir, or the scan itself throwing) — always
|
|
268
|
+
* exit 2, always `verdict: "indeterminate"` in JSON mode, with `error`
|
|
269
|
+
* naming the reason. Bad ARGUMENTS never reach here — those are caught in
|
|
270
|
+
* `mainApprovalStateCommand`'s own outer catch, before `--format` is even
|
|
271
|
+
* meaningfully known to matter, and always print usage to stderr regardless
|
|
272
|
+
* of `--format`.
|
|
273
|
+
*/
|
|
274
|
+
function emitApprovalStateCouldNotRun(format, reason) {
|
|
275
|
+
if (format === "json") {
|
|
276
|
+
console.log(JSON.stringify({
|
|
277
|
+
verdict: "indeterminate",
|
|
278
|
+
findings: [],
|
|
279
|
+
unchecked: [],
|
|
280
|
+
counts: { errors: 0, warnings: 0, unchecked: 0, filesScanned: 0, coupledFiles: 0 },
|
|
281
|
+
error: reason,
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
console.error(`[approval-state] could not run: ${reason}`);
|
|
286
|
+
}
|
|
287
|
+
return 2;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Prints the human-text or `--format json` report and returns the exit code
|
|
291
|
+
* — the one place `assessCopyApprovals`' registry findings and
|
|
292
|
+
* `checkApprovalBypass`'s source findings are merged into a single verdict.
|
|
293
|
+
* See this section's top doc comment for why the two are combined rather
|
|
294
|
+
* than reported as two separate commands.
|
|
295
|
+
*/
|
|
296
|
+
function reportApprovalState(format, registryPath, scanDirPath, registryFindings, scan, bypass) {
|
|
297
|
+
const verdict = computeApprovalStateVerdict(registryFindings, bypass);
|
|
298
|
+
const exitCode = approvalStateExitCode(verdict);
|
|
299
|
+
const registryErrors = registryFindings.filter((f) => f.severity === "error");
|
|
300
|
+
const registryWarnings = registryFindings.filter((f) => f.severity === "warning");
|
|
301
|
+
const unchecked = combineUnchecked(scan, bypass);
|
|
302
|
+
const errorCount = registryErrors.length + bypass.findings.length;
|
|
303
|
+
if (format === "json") {
|
|
304
|
+
const findings = [
|
|
305
|
+
...registryFindings.map((f) => ({ source: "registry", rule: f.rule, severity: f.severity, entryId: f.entryId, message: f.message })),
|
|
306
|
+
...bypass.findings.map((f) => ({ source: "source", rule: f.rule, severity: f.severity, file: f.file, line: f.line, message: f.detail })),
|
|
307
|
+
];
|
|
308
|
+
console.log(JSON.stringify({
|
|
309
|
+
verdict,
|
|
310
|
+
findings,
|
|
311
|
+
unchecked,
|
|
312
|
+
counts: {
|
|
313
|
+
errors: errorCount,
|
|
314
|
+
warnings: registryWarnings.length,
|
|
315
|
+
unchecked: unchecked.length,
|
|
316
|
+
filesScanned: scan.filesScanned,
|
|
317
|
+
coupledFiles: scan.coupledFiles.length,
|
|
318
|
+
},
|
|
319
|
+
}));
|
|
320
|
+
return exitCode;
|
|
321
|
+
}
|
|
322
|
+
console.log(`[approval-state] registry: ${registryPath}`);
|
|
323
|
+
console.log(`[approval-state] scan dir: ${scanDirPath}`);
|
|
324
|
+
console.log(`[approval-state] ${errorCount} error(s), ${registryWarnings.length} warning(s), ${unchecked.length} unchecked item(s), ${scan.filesScanned} file(s) scanned, ${scan.coupledFiles.length} coupled file(s).`);
|
|
325
|
+
for (const f of registryErrors) {
|
|
326
|
+
console.log(`[approval-state] [${f.rule}] ${f.entryId}: ${f.message}`);
|
|
327
|
+
}
|
|
328
|
+
for (const f of bypass.findings) {
|
|
329
|
+
console.log(`[approval-state] [${f.rule}] ${f.file}:${f.line} ${f.detail}`);
|
|
330
|
+
}
|
|
331
|
+
if (registryWarnings.length > 0) {
|
|
332
|
+
console.log(`[approval-state] warnings:`);
|
|
333
|
+
for (const f of registryWarnings) {
|
|
334
|
+
console.log(`[approval-state] [${f.rule}] ${f.entryId}: ${f.message}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (unchecked.length > 0) {
|
|
338
|
+
console.log(`[approval-state] unchecked:`);
|
|
339
|
+
for (const u of unchecked) {
|
|
340
|
+
console.log(`[approval-state] [${u.kind}] ${u.file}:${u.line} ${u.detail}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (bypass.reasons.length > 0) {
|
|
344
|
+
console.log(`[approval-state] reasons:`);
|
|
345
|
+
for (const r of bypass.reasons) {
|
|
346
|
+
console.log(`[approval-state] ${r}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
console.log(`[approval-state] verdict: ${verdict}`);
|
|
350
|
+
return exitCode;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Exported (unlike a typical CLI `main`) so this package's tests (which do
|
|
354
|
+
* not ship) can exercise the whole argv-to-exit-code contract directly, without spawning a subprocess —
|
|
355
|
+
* the same discipline `mainApproveCommand` (`approve-cli.ts`) and
|
|
356
|
+
* `mainAddressabilityCheck`/`mainPassagesCheck` (`cli.ts`) already hold to.
|
|
357
|
+
* `argv` excludes the leading `"approval-state"` token. Never throws: every
|
|
358
|
+
* failure, expected or not, is caught here and mapped to exit code 2.
|
|
359
|
+
*/
|
|
360
|
+
export function mainApprovalStateCommand(argv) {
|
|
361
|
+
try {
|
|
362
|
+
const args = parseApprovalStateArgs(argv);
|
|
363
|
+
if (args.help) {
|
|
364
|
+
console.log(APPROVAL_STATE_USAGE);
|
|
365
|
+
return 0;
|
|
366
|
+
}
|
|
367
|
+
if (args.registryFile === undefined) {
|
|
368
|
+
throw new ApprovalStateInputError("registry-file is required");
|
|
369
|
+
}
|
|
370
|
+
if (args.scanDir === undefined) {
|
|
371
|
+
throw new ApprovalStateInputError("scan-dir is required");
|
|
372
|
+
}
|
|
373
|
+
const now = parseApprovalStateNow(args.now);
|
|
374
|
+
const registryPath = resolve(process.cwd(), args.registryFile);
|
|
375
|
+
const scanDirPath = resolve(process.cwd(), args.scanDir);
|
|
376
|
+
let raw;
|
|
377
|
+
try {
|
|
378
|
+
raw = readFileSync(registryPath, "utf8");
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
return emitApprovalStateCouldNotRun(args.format, `registry file "${registryPath}" could not be read: ${errorMessage(error)}`);
|
|
382
|
+
}
|
|
383
|
+
let parsedJson;
|
|
384
|
+
try {
|
|
385
|
+
parsedJson = JSON.parse(raw);
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
return emitApprovalStateCouldNotRun(args.format, `registry file "${registryPath}" is not valid JSON: ${errorMessage(error)}`);
|
|
389
|
+
}
|
|
390
|
+
let registry;
|
|
391
|
+
try {
|
|
392
|
+
registry = parseCopyRegistry(parsedJson);
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
return emitApprovalStateCouldNotRun(args.format, errorMessage(error));
|
|
396
|
+
}
|
|
397
|
+
if (!existsSync(scanDirPath) || !statSync(scanDirPath).isDirectory()) {
|
|
398
|
+
return emitApprovalStateCouldNotRun(args.format, `scan-dir "${scanDirPath}" is not a directory`);
|
|
399
|
+
}
|
|
400
|
+
const registryFindings = assessCopyApprovals(registry, now);
|
|
401
|
+
let scan;
|
|
402
|
+
try {
|
|
403
|
+
scan = scanApprovalBypass(scanDirPath, { registryPath, extensions: args.extensions });
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
return emitApprovalStateCouldNotRun(args.format, errorMessage(error));
|
|
407
|
+
}
|
|
408
|
+
const bypass = checkApprovalBypass(scan);
|
|
409
|
+
return reportApprovalState(args.format, registryPath, scanDirPath, registryFindings, scan, bypass);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
if (error instanceof ApprovalStateInputError) {
|
|
413
|
+
console.error(`writer-check approval-state: ${error.message}`);
|
|
414
|
+
console.error(`\n${APPROVAL_STATE_USAGE}`);
|
|
415
|
+
return 2;
|
|
416
|
+
}
|
|
417
|
+
console.error(`writer-check approval-state: unexpected error: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
418
|
+
return 2;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
//# sourceMappingURL=approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `writer-check approve` — writes a `CopyRegistryEntry.approval` record so
|
|
3
|
+
* nobody has to hand-edit one. It exists as a command, not a hand-edited
|
|
4
|
+
* JSON field, for the same reason `textFingerprint` (`fingerprint.ts`) is a
|
|
5
|
+
* content hash rather than a human-maintained counter: a hand-typed record
|
|
6
|
+
* is easy to get wrong — copying yesterday's `approvedAt`, pinning `textFingerprint` to
|
|
7
|
+
* some other entry's text, or leaving `pendingOwnerReview` set after an
|
|
8
|
+
* owner actually reviewed it. Every field this command writes is either
|
|
9
|
+
* computed (`approvedAt` from the clock, `textFingerprint` from the
|
|
10
|
+
* entry's own current `text` via `computeCopyFingerprint`) or copied
|
|
11
|
+
* verbatim from an argument this command itself validated (`--delegate`,
|
|
12
|
+
* `--scope`, `--expires`) — there is no field here a human types free-form
|
|
13
|
+
* into the JSON.
|
|
14
|
+
*
|
|
15
|
+
* This module owns argument parsing, registry-file I/O, and the mutate-
|
|
16
|
+
* then-atomically-rename write; `./approval.ts`'s `isDelegateScopeItem` and
|
|
17
|
+
* `isEntryInDelegateScope` decide whether a requested id is in a delegate's
|
|
18
|
+
* declared scope, and `./schema.ts`'s `validateCopyRegistryShape` decides
|
|
19
|
+
* whether the registry file is trustworthy enough to mutate at all — this
|
|
20
|
+
* file calls both rather than re-deriving either.
|
|
21
|
+
*
|
|
22
|
+
* `main`-equivalent shape is `mainApproveCommand`, exported (unlike a
|
|
23
|
+
* typical CLI `main`) so this package's tests (which do not ship) can exercise the whole
|
|
24
|
+
* argv-to-exit-code contract directly against real `mkdtemp` fixture
|
|
25
|
+
* files, without spawning a subprocess — the same discipline every other
|
|
26
|
+
* subcommand in this package's `cli.ts` already holds to. `argv` excludes
|
|
27
|
+
* the leading `"approve"` token: `cli.ts`'s top-level dispatch calls
|
|
28
|
+
* `mainApproveCommand(argv.slice(1))` for `writer-check approve ...`, the same way `"passages"` and
|
|
29
|
+
* `"addressability"` are already dispatched there.
|
|
30
|
+
*
|
|
31
|
+
* Exit codes — a three-state contract matching every other gate/command in
|
|
32
|
+
* this package:
|
|
33
|
+
*
|
|
34
|
+
* 0 — written: every named entry was approved or revoked, and the
|
|
35
|
+
* registry file on disk now reflects it.
|
|
36
|
+
* 1 — refused: at least one named entry is unknown, is `retired`, or —
|
|
37
|
+
* for a delegate approval — falls outside `--scope`. This is
|
|
38
|
+
* ALL-OR-NOTHING: when any requested id has a problem, EVERY problem
|
|
39
|
+
* is printed and NOTHING is written, even for the other ids that
|
|
40
|
+
* would otherwise have succeeded. A partial write here would leave a
|
|
41
|
+
* consumer's own audit trail unable to tell whether a whole
|
|
42
|
+
* requested batch was actually reviewed together.
|
|
43
|
+
* 2 — could not run: bad arguments (see below), or the registry file is
|
|
44
|
+
* missing/unreadable, not valid JSON, or fails
|
|
45
|
+
* `validateCopyRegistryShape`. Nothing is written in this state
|
|
46
|
+
* either — this command never mutates a registry it does not first
|
|
47
|
+
* fully trust.
|
|
48
|
+
*
|
|
49
|
+
* Bad-argument cases that produce exit 2, all before the registry file is
|
|
50
|
+
* ever opened: no registry-file argument, zero entry ids, neither `--by`
|
|
51
|
+
* nor `--revoke` (or both), `--by` other than `owner`/`delegate`, `--by
|
|
52
|
+
* owner` combined with `--delegate`/`--scope`/`--expires`, `--by delegate`
|
|
53
|
+
* missing `--delegate` or `--scope`, an empty `--delegate` value, a
|
|
54
|
+
* `--scope` item that fails `isDelegateScopeItem` or an empty scope list,
|
|
55
|
+
* `--revoke` combined with any of `--by`/`--delegate`/`--scope`/
|
|
56
|
+
* `--expires`, an `--expires` value that is not a well-formed ISO 8601 UTC
|
|
57
|
+
* timestamp or is not strictly after the current time, or an unknown flag.
|
|
58
|
+
* `--help`/`-h` always prints usage and returns 0, taking precedence over
|
|
59
|
+
* every other check reachable in the same parse. Any other unexpected
|
|
60
|
+
* exception is caught and mapped to exit 2 as well — this command never
|
|
61
|
+
* lets an uncaught error double as a "something went wrong" report.
|
|
62
|
+
*/
|
|
63
|
+
export declare const APPROVE_USAGE = "Usage: writer-check approve <registry-file> <entry-id>... --by owner\n or: writer-check approve <registry-file> <entry-id>... --by delegate --delegate <id> --scope <ns>[,<ns>...] [--expires <ISO>]\n or: writer-check approve <registry-file> <entry-id>... --revoke\n\n registry-file Path to a CopyRegistry JSON file. Required.\n entry-id One or more entry ids to approve or revoke (repeatable positional argument; duplicates are deduplicated). Required.\n\nOptions:\n --help Print this message and exit 0.\n --by <owner|delegate> Who is recording this approval. Exactly one of --by or --revoke is required.\n --delegate <id> Opaque delegate identifier, never a personal name. Required (and only allowed) with --by delegate.\n --scope <ns>[,<ns>...] One or more dot-separated entry-id namespaces the delegate may approve (repeatable; a comma-separated value and repeated flags union). Required (and only allowed) with --by delegate.\n --expires <ISO> ISO 8601 UTC timestamp (YYYY-MM-DDTHH:MM:SSZ, optionally with milliseconds) after which the delegate approval expires. Must be strictly after the current time. Only allowed with --by delegate.\n --revoke Return every named entry to \"draft\" and delete its approval record. Cannot be combined with --by, --delegate, --scope, or --expires.\n\nApproving an entry that already carries a delegate's approval record with\n--by owner replaces that record with the owner's own \u2014 this is how a\npending delegate approval is durably confirmed.\n\nExit codes: 0 = written (every named entry approved or revoked; the\nregistry file was updated), 1 = refused (an unknown, retired, or \u2014 for a\ndelegate approval \u2014 out-of-scope entry id was named; every problem is\nprinted and nothing is written), 2 = could not run (bad arguments, or the\nregistry file is missing/unreadable/not valid JSON/fails schema\nvalidation; nothing is written).\n";
|
|
64
|
+
/**
|
|
65
|
+
* Exported (unlike a typical CLI `main`) so this package's tests (which do
|
|
66
|
+
* not ship) can exercise the whole argv-to-exit-code contract directly against real
|
|
67
|
+
* `mkdtemp` fixture files, without spawning a subprocess — see this file's
|
|
68
|
+
* top doc comment for the full exit-code contract. `argv` excludes the
|
|
69
|
+
* leading `"approve"` subcommand token. `io.now` lets a test pin the clock
|
|
70
|
+
* that `approvedAt` and `--expires` validation are measured against;
|
|
71
|
+
* omitted, it defaults to `new Date()`.
|
|
72
|
+
*
|
|
73
|
+
* Never throws: every failure, expected or not, is caught here and mapped
|
|
74
|
+
* to an exit code — bad arguments and an untrustworthy registry file both
|
|
75
|
+
* become 2 with nothing written, a refused (but well-formed) request
|
|
76
|
+
* becomes 1 with nothing written, and a clean run becomes 0 with the
|
|
77
|
+
* registry file updated on disk.
|
|
78
|
+
*/
|
|
79
|
+
export declare function mainApproveCommand(argv: string[], io?: {
|
|
80
|
+
now?: Date;
|
|
81
|
+
}): number;
|
|
82
|
+
//# sourceMappingURL=approve-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approve-cli.d.ts","sourceRoot":"","sources":["../src/approve-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AASH,eAAO,MAAM,aAAa,46DAyBzB,CAAC;AA+SF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE;IAAE,GAAG,CAAC,EAAE,IAAI,CAAA;CAAO,GAAG,MAAM,CA4DlF"}
|