bareloop 0.41.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -1
- package/README.md +13 -21
- package/bareloop.context.md +44 -4
- package/package.json +4 -3
- package/src/authorflow.js +337 -29
- package/src/authoring.js +92 -7
- package/src/authorjob.js +78 -16
- package/src/authorreadout.js +89 -5
- package/src/authorrun.js +14 -5
- package/src/authorscout.js +108 -0
- package/src/declaredclose.js +23 -12
- package/src/facts.js +317 -0
- package/src/factwords.js +129 -0
- package/src/inputlook.js +301 -0
- package/src/job.js +2 -1
- package/src/jobblock.js +7 -2
- package/src/kinds.js +84 -3
- package/src/labelscan.js +170 -0
- package/src/pagefetch.js +125 -0
- package/src/panel/authorroutes.js +15 -2
- package/src/panel/authorsession.js +99 -22
- package/src/panel/index.html +33 -7
- package/src/panel/jobplan.js +34 -11
- package/src/panel/server.js +19 -3
- package/src/planrun.js +3 -2
- package/src/rubricstep.js +804 -0
- package/src/source.js +166 -1
- package/src/u-readout.js +1 -1
- package/src/userrun.js +14 -2
- package/types/authorflow.d.ts +267 -13
- package/types/authoring.d.ts +29 -1
- package/types/authorjob.d.ts +7 -5
- package/types/authorreadout.d.ts +27 -2
- package/types/authorscout.d.ts +70 -0
- package/types/declaredclose.d.ts +4 -2
- package/types/facts.d.ts +71 -0
- package/types/factwords.d.ts +42 -0
- package/types/inputlook.d.ts +238 -0
- package/types/jobblock.d.ts +8 -1
- package/types/labelscan.d.ts +38 -0
- package/types/pagefetch.d.ts +71 -0
- package/types/panel/authorsession.d.ts +18 -2
- package/types/panel/jobplan.d.ts +12 -4
- package/types/panel/server.d.ts +4 -2
- package/types/rubricstep.d.ts +286 -0
- package/types/source.d.ts +94 -0
- package/types/userrun.d.ts +11 -0
package/src/source.js
CHANGED
|
@@ -23,14 +23,16 @@
|
|
|
23
23
|
// on rather than catch.
|
|
24
24
|
|
|
25
25
|
import { createHash } from 'node:crypto';
|
|
26
|
+
import { tmpdir } from 'node:os';
|
|
26
27
|
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
27
|
-
import { mkdir, readdir, readFile, writeFile, lstat, stat, chmod, access, copyFile, cp, open, rm, realpath, readlink, symlink, constants as fsConstants } from 'node:fs/promises';
|
|
28
|
+
import { mkdir, mkdtemp, readdir, readFile, writeFile, lstat, stat, chmod, access, copyFile, cp, open, rm, realpath, readlink, symlink, constants as fsConstants } from 'node:fs/promises';
|
|
28
29
|
import { dirname, join, relative, resolve, sep, basename, isAbsolute } from 'node:path';
|
|
29
30
|
import { git } from './kinds.js';
|
|
30
31
|
import { addWorktree, hideBareloopDir } from './worktree.js';
|
|
31
32
|
import { MAX_BUFFER } from './kinds.js';
|
|
32
33
|
import { PROVIDER_TIMEOUT_MS } from './clock.js';
|
|
33
34
|
import { scanSecrets, SECRET_PATTERNS, SECRET_PATTERN_NAMES } from './validate.js';
|
|
35
|
+
import { fetchPageText, isWebUrl } from './pagefetch.js';
|
|
34
36
|
|
|
35
37
|
/** @typedef {{stop: string, code: string}} SourceRefusal a named, non-throwing stop */
|
|
36
38
|
|
|
@@ -1223,3 +1225,166 @@ export async function proveInputFiles(inputs) {
|
|
|
1223
1225
|
}
|
|
1224
1226
|
return { ok: true, repo, root };
|
|
1225
1227
|
}
|
|
1228
|
+
|
|
1229
|
+
/** the per-file ceiling for a rubric input: bareguard's `cited` check searches a quote in a source only up to this size
|
|
1230
|
+
* (its `quoteSourceMaxBytes`); a bigger file could never be cited from, so the door refuses it at $0 */
|
|
1231
|
+
export const RUBRIC_INPUT_MAX_BYTES = 5 * 1024 * 1024;
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* Does this source manifest belong to a rubric job on local files (`proveRubricInputs` wrote it)? One spelling for both
|
|
1235
|
+
* doors (the panel session and the CLI author run): a non-repo source that froze numbered, labelled input files.
|
|
1236
|
+
* @param {Record<string, any>|null|undefined} manifest
|
|
1237
|
+
* @returns {boolean}
|
|
1238
|
+
*/
|
|
1239
|
+
export function isRubricFilesManifest(manifest) {
|
|
1240
|
+
return !!manifest && manifest.kind !== 'repo' && Array.isArray(manifest.inputs) && manifest.inputs.length > 0;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/**
|
|
1244
|
+
* PART 1a (docs/product/ITEM33-BUILD.md, "Addendum 2026-10-12"): the front door for a RUBRIC job whose inputs are the
|
|
1245
|
+
* person's own LOCAL FILES, not a repo. Each numbered `label: /absolute/file` line is proven at $0 and frozen into the
|
|
1246
|
+
* same hidden-git tree a plain folder gets (`prepareSource`, which also refuses a secret shape, an environment file
|
|
1247
|
+
* and a symlink), under `input/`; the manifest then also records `inputs: [{label, rel, sha256}]` — the bytes the
|
|
1248
|
+
* rubric's `cited` check will search a quote in, hashed once, here. A line that is a web address is fetched ONCE with
|
|
1249
|
+
* barebrowse (PART 1c, read only) and its text frozen under `input/` like a file (the manifest row also records its `url`,
|
|
1250
|
+
* so run start can fetch it again); a `search:` line is a named refusal (a later build); so is a relative path, a missing file, a folder, a file over
|
|
1251
|
+
* {@link RUBRIC_INPUT_MAX_BYTES}, and text that is not valid UTF-8. Every refusal is plain words naming the line and
|
|
1252
|
+
* ends "Nothing spent." — nothing here touches a model.
|
|
1253
|
+
* @param {{inputs: {n: number, label: string, value: string}[], into: string, destination?: string, fetchUrl?: (url: string) => Promise<{ok: true, text: string, url: string}|{ok: false, error: string}>}} o `fetchUrl` - how a web address is read (default barebrowse via `fetchPageText`; a test injects a stub, never the network)
|
|
1254
|
+
* @returns {Promise<{ok: true, prep: {stop: null, into: string, tree: string, manifestPath: string, manifest: Record<string, any>}}|{ok: false, error: string, code: string}>}
|
|
1255
|
+
*/
|
|
1256
|
+
export async function proveRubricInputs({ inputs, into, destination, fetchUrl = (u) => fetchPageText(u) }) {
|
|
1257
|
+
/** @param {string} code @param {number} n @param {string} msg @returns {{ok: false, error: string, code: string}} */
|
|
1258
|
+
const bad = (code, n, msg) => ({ ok: false, code, error: `line ${n}: ${msg} Nothing spent.` });
|
|
1259
|
+
if (!Array.isArray(inputs) || inputs.length === 0) return { ok: false, code: 'inputs-missing', error: 'Inputs is required. Nothing spent.' };
|
|
1260
|
+
/** @type {Set<string>} */
|
|
1261
|
+
const labels = new Set();
|
|
1262
|
+
/** @type {{label: string, name: string, buf: Buffer, url?: string}[]} */
|
|
1263
|
+
const staged = [];
|
|
1264
|
+
/** @type {Set<string>} */
|
|
1265
|
+
const names = new Set();
|
|
1266
|
+
const utf8 = new TextDecoder('utf-8', { fatal: true });
|
|
1267
|
+
for (const inp of inputs) {
|
|
1268
|
+
const { n, label, value } = inp;
|
|
1269
|
+
if (/^search$/i.test(label) || (/^[a-z][a-z0-9+.-]*:\/\//i.test(value) && !isWebUrl(value))) {
|
|
1270
|
+
return bad('web-input', n, 'a search, or an address that is not http or https, is not an input yet.');
|
|
1271
|
+
}
|
|
1272
|
+
if (labels.has(label.toLowerCase())) return bad('label-repeat', n, `the label "${label}" is already used by an earlier line.`);
|
|
1273
|
+
labels.add(label.toLowerCase());
|
|
1274
|
+
if (isWebUrl(value)) {
|
|
1275
|
+
const got = await fetchUrl(value.trim());
|
|
1276
|
+
if (!got.ok) return bad('input-url-unreadable', n, `${got.error.replace(/[.\s]+$/, '')}.`);
|
|
1277
|
+
const pageBuf = Buffer.from(got.text, 'utf8');
|
|
1278
|
+
const leak = secretPatternNames(got.text);
|
|
1279
|
+
if (leak.length) return bad('input-url-secret', n, `the page at ${got.url} carries a known secret shape (${leak.join(', ')}) - refused, secrets never enter a job.`);
|
|
1280
|
+
const stem = label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'page';
|
|
1281
|
+
const pageName = names.has(`${stem}.txt`) ? `${n}-${stem}.txt` : `${stem}.txt`;
|
|
1282
|
+
names.add(pageName);
|
|
1283
|
+
staged.push({ label, name: pageName, buf: pageBuf, url: got.url });
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
if (!isAbsolute(value)) return bad('input-not-absolute', n, `${value} must be the full path of a file, like /home/me/notes.md.`);
|
|
1287
|
+
const abs = resolve(value);
|
|
1288
|
+
let ls;
|
|
1289
|
+
try { ls = await lstat(abs); } catch { return bad('input-missing', n, `${abs} does not exist.`); }
|
|
1290
|
+
if (ls.isSymbolicLink()) return bad('input-symlink', n, `${abs} is a symlink — refused, never followed.`);
|
|
1291
|
+
if (!ls.isFile()) return bad('input-not-file', n, `${abs} is not a file — each input line is one file.`);
|
|
1292
|
+
const base = basename(abs);
|
|
1293
|
+
if (ENV_FILE.test(base)) return bad('input-env-file', n, `${abs} is an environment file — refused by name, whatever it contains.`);
|
|
1294
|
+
if (ls.size > RUBRIC_INPUT_MAX_BYTES) return bad('input-oversize', n, `${abs} is over the ${RUBRIC_INPUT_MAX_BYTES / (1024 * 1024)} MiB limit for a quoted input.`);
|
|
1295
|
+
const buf = await readFile(abs);
|
|
1296
|
+
try { utf8.decode(buf); } catch { return bad('input-not-utf8', n, `${abs} is not UTF-8 text — text files only for now.`); }
|
|
1297
|
+
if (hasNulByte(buf)) return bad('input-not-text', n, `${abs} is not a text file — text files only for now.`);
|
|
1298
|
+
// two files with one name keep the line number in front, so neither overwrites the other under input/
|
|
1299
|
+
const name = names.has(base) ? `${n}-${base}` : base;
|
|
1300
|
+
names.add(name);
|
|
1301
|
+
staged.push({ label, name, buf });
|
|
1302
|
+
}
|
|
1303
|
+
const stage = await mkdtemp(join(tmpdir(), 'bareloop-rubric-inputs-'));
|
|
1304
|
+
try {
|
|
1305
|
+
for (const f of staged) await writeFile(join(stage, f.name), f.buf);
|
|
1306
|
+
const prep = await prepareSource({ source: stage, into, ...(destination === undefined ? {} : { destination }) });
|
|
1307
|
+
if (prep.stop !== null) return { ok: false, code: prep.code, error: prep.stop };
|
|
1308
|
+
// `source` names the person's own first file, never the scratch staging folder that was already removed
|
|
1309
|
+
const manifest = {
|
|
1310
|
+
...prep.manifest,
|
|
1311
|
+
source: isWebUrl(inputs[0].value) ? inputs[0].value.trim() : resolve(inputs[0].value),
|
|
1312
|
+
inputs: staged.map((f) => ({ label: f.label, rel: `input/${f.name}`, sha256: sha256Hex(f.buf), ...(f.url === undefined ? {} : { url: f.url }) })),
|
|
1313
|
+
};
|
|
1314
|
+
await writeFile(prep.manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
1315
|
+
return { ok: true, prep: { ...prep, manifest } };
|
|
1316
|
+
} finally {
|
|
1317
|
+
await rm(stage, { recursive: true, force: true });
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/**
|
|
1322
|
+
* Is this Inputs value a path to an existing regular FILE on this machine (not a repo folder, not a web address, not a
|
|
1323
|
+
* missing path)? The $0 peek that tells a rubric job on local files from a rubric job on a repo before a session exists;
|
|
1324
|
+
* `proveRubricInputs` makes the authoritative, line-by-line proof.
|
|
1325
|
+
* @param {string} value
|
|
1326
|
+
* @returns {boolean}
|
|
1327
|
+
*/
|
|
1328
|
+
export function looksLikeLocalFile(value) {
|
|
1329
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value) || !isAbsolute(value)) return false;
|
|
1330
|
+
try { return lstatSync(resolve(value)).isFile(); } catch { return false; }
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Is this Inputs value one a rubric job can work on without a repo: a regular FILE on this machine, or a web address (PART 1c)?
|
|
1335
|
+
* @param {string} value
|
|
1336
|
+
* @returns {boolean}
|
|
1337
|
+
*/
|
|
1338
|
+
export function looksLikeRubricInput(value) {
|
|
1339
|
+
return isWebUrl(value) || looksLikeLocalFile(value);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* RUN START for a rubric job with web inputs (PART 1c, RF22): fetch every web input AGAIN and make that text the run's frozen input
|
|
1344
|
+
* BEFORE the worker starts. The drafting copy was only for the look (prices change); the close grades, and the worker reads, THIS
|
|
1345
|
+
* copy. It is written into the prepared tree, committed as the new seed (so the page is never read as a worker write), and
|
|
1346
|
+
* `source.json` is rewritten (new sha256 per input, new seed, `draftSha256` keeps what the look saw). Nothing but the text of a
|
|
1347
|
+
* recorded web address is ever refetched; a job with only file inputs returns at once and changes nothing.
|
|
1348
|
+
* @param {{into: string, fetchUrl?: (url: string) => Promise<{ok: true, text: string, url: string}|{ok: false, error: string}>}} o `into` - the prepared copy (source.json + tree/)
|
|
1349
|
+
* @returns {Promise<{ok: true, refreshed: string[], seed: string|null}|{ok: false, error: string}>}
|
|
1350
|
+
*/
|
|
1351
|
+
export async function refreezeUrlInputs({ into, fetchUrl = (u) => fetchPageText(u) }) {
|
|
1352
|
+
const read = await readSourceManifest(into);
|
|
1353
|
+
if (read.stop !== null) return { ok: false, error: read.stop };
|
|
1354
|
+
if (!read.present || !read.manifest) return { ok: true, refreshed: [], seed: null };
|
|
1355
|
+
const manifest = read.manifest;
|
|
1356
|
+
const rows = Array.isArray(manifest.inputs) ? manifest.inputs.filter((/** @type {any} */ r) => r && typeof r.url === 'string' && r.url !== '') : [];
|
|
1357
|
+
if (rows.length === 0) return { ok: true, refreshed: [], seed: null };
|
|
1358
|
+
const tree = join(resolve(into), 'tree');
|
|
1359
|
+
/** @type {{row: any, buf: Buffer}[]} */
|
|
1360
|
+
const fresh = [];
|
|
1361
|
+
for (const row of rows) {
|
|
1362
|
+
const got = await fetchUrl(row.url);
|
|
1363
|
+
if (!got.ok) return { ok: false, error: `Input "${row.label}": ${got.error}. The run did not start. Nothing spent.` };
|
|
1364
|
+
const leak = secretPatternNames(got.text);
|
|
1365
|
+
if (leak.length) return { ok: false, error: `Input "${row.label}": the page at ${got.url} carries a known secret shape (${leak.join(', ')}) - refused, secrets never enter a job. The run did not start. Nothing spent.` };
|
|
1366
|
+
fresh.push({ row, buf: Buffer.from(got.text, 'utf8') });
|
|
1367
|
+
}
|
|
1368
|
+
for (const f of fresh) await writeFile(join(tree, f.row.rel), f.buf);
|
|
1369
|
+
const add = await git(tree, [...NO_HOOKS, 'add', '-A', '-f']);
|
|
1370
|
+
if (!add.ok) return { ok: false, error: `the fresh page copy could not be staged: ${add.err}. The run did not start. Nothing spent.` };
|
|
1371
|
+
const commit = await git(tree, [...GIT_IDENTITY, ...NO_HOOKS, 'commit', '-q', '--allow-empty', '-m', 'bareloop: run start - fresh copy of the web inputs']);
|
|
1372
|
+
if (!commit.ok) return { ok: false, error: `the fresh page copy could not be committed: ${commit.err}. The run did not start. Nothing spent.` };
|
|
1373
|
+
const head = await git(tree, ['rev-parse', 'HEAD']);
|
|
1374
|
+
if (!head.ok) return { ok: false, error: `git rev-parse HEAD failed in ${tree}: ${head.err}. The run did not start. Nothing spent.` };
|
|
1375
|
+
const seed = head.out.trim();
|
|
1376
|
+
const byLabel = new Map(fresh.map((f) => [f.row.label, f.buf]));
|
|
1377
|
+
const inputs = manifest.inputs.map((/** @type {any} */ r) => {
|
|
1378
|
+
const buf = byLabel.get(r.label);
|
|
1379
|
+
return buf === undefined || typeof r.url !== 'string' ? r : { ...r, draftSha256: r.draftSha256 ?? r.sha256, sha256: sha256Hex(buf) };
|
|
1380
|
+
});
|
|
1381
|
+
const files = Array.isArray(manifest.files) ? manifest.files.map((/** @type {any} */ m) => {
|
|
1382
|
+
const hit = inputs.find((/** @type {any} */ r) => typeof r.url === 'string' && r.rel === `input/${m.path}`);
|
|
1383
|
+
if (!hit) return m;
|
|
1384
|
+
const buf = byLabel.get(hit.label);
|
|
1385
|
+
return buf === undefined ? m : { ...m, bytes: buf.length, sha256: sha256Hex(buf) };
|
|
1386
|
+
}) : manifest.files;
|
|
1387
|
+
const next = { ...manifest, inputs, files, seed, refrozenAt: new Date().toISOString() };
|
|
1388
|
+
await writeFile(join(resolve(into), 'source.json'), `${JSON.stringify(next, null, 2)}\n`);
|
|
1389
|
+
return { ok: true, refreshed: rows.map((/** @type {any} */ r) => r.label), seed };
|
|
1390
|
+
}
|
package/src/u-readout.js
CHANGED
|
@@ -202,7 +202,7 @@ export function reviewDoorPackage({ door, diff }) {
|
|
|
202
202
|
const out = [];
|
|
203
203
|
out.push(`REVIEW DOOR — this run's close minted ${String(d.outcome ?? 'a verdict')} and it STANDS. Nothing you do here changes it.`);
|
|
204
204
|
if (held) {
|
|
205
|
-
out.push(' held this
|
|
205
|
+
out.push(' held this is a rubric (soft-green) job, so its green earns NO reuse and NO learning credit until you accept it, whether or not a judge ran');
|
|
206
206
|
}
|
|
207
207
|
// the body: what the machine judged, what moved, and the lines themselves —
|
|
208
208
|
// rendered by the SAME code the pause screen uses, off the same record shape.
|
package/src/userrun.js
CHANGED
|
@@ -42,7 +42,7 @@ import { makeSpine } from './spine.js';
|
|
|
42
42
|
// signed into the job spec — a job is a repeatable SHAPE, the source/
|
|
43
43
|
// destination a per-run value, exactly like `bareloop run <bundle> --repo
|
|
44
44
|
// <path>` varies the repo without touching the bundle's signature).
|
|
45
|
-
import { readSourceManifest, frontDoorFromManifest, proveDestination, copyOut } from './source.js';
|
|
45
|
+
import { readSourceManifest, frontDoorFromManifest, proveDestination, copyOut, refreezeUrlInputs } from './source.js';
|
|
46
46
|
import { scanSecrets, redactSecrets } from './validate.js';
|
|
47
47
|
import { runBehaviour, formatBehaviour } from './behaviour.js';
|
|
48
48
|
// the three doors' SEMANTICS live in the library; this script surfaces them
|
|
@@ -235,6 +235,7 @@ class ExitSignal extends Error {
|
|
|
235
235
|
* @property {any} [provider] the TEST SEAM: supplying this skips the real-key
|
|
236
236
|
* check entirely, the same shape `src/cli.js`'s `main(argv, deps)` uses.
|
|
237
237
|
* @property {(tier: string) => any} [providerFor]
|
|
238
|
+
* @property {(url: string) => Promise<{ok: true, text: string, url: string}|{ok: false, error: string}>} [fetchUrl] PART 1c - a test seam: how a web input is read at run start (production: barebrowse)
|
|
238
239
|
* @property {any} [judgeProvider]
|
|
239
240
|
* @property {string} [runlistHome] F196 — a test seam for `appendRun`'s
|
|
240
241
|
* (src/runlist.js) `home` override, the same shape `deps.provider` already
|
|
@@ -2592,13 +2593,24 @@ export async function main(argv, deps = {}) {
|
|
|
2592
2593
|
+ 'with exactly one source-*/ sibling.');
|
|
2593
2594
|
}
|
|
2594
2595
|
const into = candidates[0];
|
|
2595
|
-
|
|
2596
|
+
let manifestRead = await readSourceManifest(into);
|
|
2596
2597
|
if (manifestRead.stop !== null) {
|
|
2597
2598
|
die(`--spec ${specPath}: ${manifestRead.code} — ${manifestRead.stop}`);
|
|
2598
2599
|
}
|
|
2599
2600
|
if (!manifestRead.present || !manifestRead.manifest) {
|
|
2600
2601
|
die(`--spec ${specPath}: ${into} carries no source.json — not a prepared copy this runner can trust`);
|
|
2601
2602
|
}
|
|
2603
|
+
// PART 1c (RF22): a FRESH run of a rubric job with web inputs fetches each address again and makes that text the run's frozen
|
|
2604
|
+
// input BEFORE the worker starts (the drafting copy was only for the look). A resume / door / decision keeps the copy its
|
|
2605
|
+
// first start froze. A refusal is plain words, $0.
|
|
2606
|
+
if (argFlag('resume') === null && argFlag('door') === null && argFlag('decide') === null) {
|
|
2607
|
+
const fresh = await refreezeUrlInputs({ into, ...(deps.fetchUrl ? { fetchUrl: deps.fetchUrl } : {}) });
|
|
2608
|
+
if (!fresh.ok) die(`--spec ${specPath}: ${fresh.error}`);
|
|
2609
|
+
if (fresh.refreshed.length > 0) {
|
|
2610
|
+
manifestRead = await readSourceManifest(into);
|
|
2611
|
+
if (manifestRead.stop !== null || !manifestRead.manifest) die(`--spec ${specPath}: the run copy's record could not be read after the fresh fetch`);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2602
2614
|
const seed = manifestRead.manifest.seed;
|
|
2603
2615
|
if (typeof seed !== 'string' || !seed) {
|
|
2604
2616
|
die(`--spec ${specPath}: ${into}/source.json carries no seed commit — not a prepared copy this runner can trust`);
|
package/types/authorflow.d.ts
CHANGED
|
@@ -63,13 +63,16 @@ export function labelsFor(verdictType: string): Record<string | number, string>;
|
|
|
63
63
|
* close — one source, never a second hand-typed list — rendered through the
|
|
64
64
|
* frozen line in {@link GUARD_DESCRIPTIONS}, plus the write fence (a real,
|
|
65
65
|
* signed fence — {@link writeScopeBlock}'s own subject) when one is set.
|
|
66
|
-
* @param {{verdictType: string, lang: string, writeScope?: string[]|null}} o
|
|
66
|
+
* @param {{verdictType: string, lang: string, writeScope?: string[]|null, jobLines?: {n: number}[]|null}} o
|
|
67
67
|
* @returns {string[]}
|
|
68
68
|
*/
|
|
69
|
-
export function confirmProtections({ verdictType, lang, writeScope }: {
|
|
69
|
+
export function confirmProtections({ verdictType, lang, writeScope, jobLines }: {
|
|
70
70
|
verdictType: string;
|
|
71
71
|
lang: string;
|
|
72
72
|
writeScope?: string[] | null;
|
|
73
|
+
jobLines?: {
|
|
74
|
+
n: number;
|
|
75
|
+
}[] | null;
|
|
73
76
|
}): string[];
|
|
74
77
|
/**
|
|
75
78
|
* The mechanical seam between the catalogue and the schema, read BOTH ways: a
|
|
@@ -107,12 +110,15 @@ export function schemaCoverage(catalogue?: Record<string, any>): {
|
|
|
107
110
|
* and failing loudly beats emitting a schema that quietly cannot say something
|
|
108
111
|
* the validator will then demand.
|
|
109
112
|
* `questions` (P7 item 5, last drafting try only) adds the optional `questions` property; absent, the schema is byte-identical.
|
|
113
|
+
* `kinds` (Part 1a) narrows the branches to the kinds a language may use (`catalogueForLang`) WITHOUT narrowing the coverage check,
|
|
114
|
+
* which stays over the whole catalogue; null (the default) = no narrowing.
|
|
110
115
|
* @param {Record<string, any>} [catalogue]
|
|
111
|
-
* @param {{verdictType?: string|null, questions?: boolean}} [o]
|
|
116
|
+
* @param {{verdictType?: string|null, questions?: boolean, kinds?: string[]|null}} [o]
|
|
112
117
|
*/
|
|
113
|
-
export function declarationSchema(catalogue?: Record<string, any>, { verdictType, questions }?: {
|
|
118
|
+
export function declarationSchema(catalogue?: Record<string, any>, { verdictType, questions, kinds }?: {
|
|
114
119
|
verdictType?: string | null;
|
|
115
120
|
questions?: boolean;
|
|
121
|
+
kinds?: string[] | null;
|
|
116
122
|
}): {
|
|
117
123
|
type: string;
|
|
118
124
|
properties: {
|
|
@@ -192,6 +198,11 @@ export function declarationSchema(catalogue?: Record<string, any>, { verdictType
|
|
|
192
198
|
minimum: number;
|
|
193
199
|
description: string;
|
|
194
200
|
};
|
|
201
|
+
rule: {
|
|
202
|
+
type: string;
|
|
203
|
+
minimum: number;
|
|
204
|
+
description: string;
|
|
205
|
+
};
|
|
195
206
|
reason: {
|
|
196
207
|
type: string;
|
|
197
208
|
minLength: number;
|
|
@@ -211,14 +222,15 @@ export function declarationSchema(catalogue?: Record<string, any>, { verdictType
|
|
|
211
222
|
* The output channel. It records and acknowledges; it takes no action, which is
|
|
212
223
|
* why handing it to a toolless authoring call does not reopen D3.
|
|
213
224
|
* @param {{calls: any[]}} box
|
|
214
|
-
* @param {{catalogue?: Record<string, any>, verdictType?: string|null, questions?: boolean}} [o]
|
|
225
|
+
* @param {{catalogue?: Record<string, any>, verdictType?: string|null, questions?: boolean, kinds?: string[]|null}} [o]
|
|
215
226
|
*/
|
|
216
227
|
export function declarationTool(box: {
|
|
217
228
|
calls: any[];
|
|
218
|
-
}, { catalogue, verdictType, questions }?: {
|
|
229
|
+
}, { catalogue, verdictType, questions, kinds }?: {
|
|
219
230
|
catalogue?: Record<string, any>;
|
|
220
231
|
verdictType?: string | null;
|
|
221
232
|
questions?: boolean;
|
|
233
|
+
kinds?: string[] | null;
|
|
222
234
|
}): {
|
|
223
235
|
name: string;
|
|
224
236
|
description: string;
|
|
@@ -301,6 +313,11 @@ export function declarationTool(box: {
|
|
|
301
313
|
minimum: number;
|
|
302
314
|
description: string;
|
|
303
315
|
};
|
|
316
|
+
rule: {
|
|
317
|
+
type: string;
|
|
318
|
+
minimum: number;
|
|
319
|
+
description: string;
|
|
320
|
+
};
|
|
304
321
|
reason: {
|
|
305
322
|
type: string;
|
|
306
323
|
minLength: number;
|
|
@@ -400,6 +417,7 @@ export function writeScopeBlock(writeScope: string[]): string;
|
|
|
400
417
|
* never merge.
|
|
401
418
|
* @param {{checks: string[], checkItems?: {text: string, fromLine: number[]|null}[], protections: string[], openQuestions?: string[], notChecked?: string[],
|
|
402
419
|
* answeredQuestions?: string[]}} confirmed
|
|
420
|
+
* @param {{numbered?: boolean}} [o] `numbered` (a rubric job on files): each confirmed check carries its `#n`, which a part cites in `covers`
|
|
403
421
|
*/
|
|
404
422
|
export function confirmedBlock(confirmed: {
|
|
405
423
|
checks: string[];
|
|
@@ -411,6 +429,8 @@ export function confirmedBlock(confirmed: {
|
|
|
411
429
|
openQuestions?: string[];
|
|
412
430
|
notChecked?: string[];
|
|
413
431
|
answeredQuestions?: string[];
|
|
432
|
+
}, { numbered }?: {
|
|
433
|
+
numbered?: boolean;
|
|
414
434
|
}): string;
|
|
415
435
|
/**
|
|
416
436
|
* P7: the job-line tagging order (model-facing, registered). Rule 1 of the job block: every stage the drafter writes
|
|
@@ -439,12 +459,14 @@ export function jobLineTagBlock(count: number): string;
|
|
|
439
459
|
* @param {{answers: Record<string|number, string>, questions?: Record<string|number, string>,
|
|
440
460
|
* facts: any, listingBlock: string, lang: string, verdictType: string,
|
|
441
461
|
* guards: {name: string, kind: string, params: Record<string, any>, fill: string[]}[],
|
|
442
|
-
* ownedEnvNames?: string[], mode?: 'tool'|'text', catalogue?: Record<string, any
|
|
462
|
+
* ownedEnvNames?: string[], mode?: 'tool'|'text', catalogue?: Record<string, any>|null,
|
|
443
463
|
* writeScope?: string[]|null,
|
|
444
464
|
* jobLines?: {n: number}[]|null, inputs?: {n: number, label: string, value: string}[]|null,
|
|
445
|
-
* confirmed?: {checks: string[], checkItems?: {text: string, fromLine: number[]|null}[], protections: string[], openQuestions?: string[], notChecked?: string[], answeredQuestions?: string[]}|null
|
|
465
|
+
* confirmed?: {checks: string[], checkItems?: {text: string, fromLine: number[]|null}[], protections: string[], openQuestions?: string[], notChecked?: string[], answeredQuestions?: string[]}|null,
|
|
466
|
+
* labelLists?: string[]|null,
|
|
467
|
+
* }} o
|
|
446
468
|
*/
|
|
447
|
-
export function authorPrompt({ answers, questions, facts, listingBlock, lang, verdictType, guards, ownedEnvNames, mode, catalogue, writeScope, jobLines, inputs, confirmed, }: {
|
|
469
|
+
export function authorPrompt({ answers, questions, facts, listingBlock, lang, verdictType, guards, ownedEnvNames, mode, catalogue: catalogueIn, writeScope, jobLines, inputs, confirmed, labelLists, }: {
|
|
448
470
|
answers: Record<string | number, string>;
|
|
449
471
|
questions?: Record<string | number, string>;
|
|
450
472
|
facts: any;
|
|
@@ -459,7 +481,7 @@ export function authorPrompt({ answers, questions, facts, listingBlock, lang, ve
|
|
|
459
481
|
}[];
|
|
460
482
|
ownedEnvNames?: string[];
|
|
461
483
|
mode?: "tool" | "text";
|
|
462
|
-
catalogue?: Record<string, any
|
|
484
|
+
catalogue?: Record<string, any> | null;
|
|
463
485
|
writeScope?: string[] | null;
|
|
464
486
|
jobLines?: {
|
|
465
487
|
n: number;
|
|
@@ -480,6 +502,45 @@ export function authorPrompt({ answers, questions, facts, listingBlock, lang, ve
|
|
|
480
502
|
notChecked?: string[];
|
|
481
503
|
answeredQuestions?: string[];
|
|
482
504
|
} | null;
|
|
505
|
+
labelLists?: string[] | null;
|
|
506
|
+
}): string;
|
|
507
|
+
/**
|
|
508
|
+
* The composer prompt for a rubric job on local files (Part 1a, docs/product/ITEM33-BUILD.md). There is no repository
|
|
509
|
+
* and no survey: the facts are the numbered inputs and the listing is the frozen tree (input/ files, output/). It states
|
|
510
|
+
* the step-file contract the worker is also told ({@link stepFileContract}), so the stage and the file cannot drift.
|
|
511
|
+
* @param {{answers: Record<string|number, string>, questions?: Record<string|number, string>, facts: any, listingBlock: string,
|
|
512
|
+
* verdictType: string, guards: {name: string, kind: string, params: Record<string, any>, fill: string[]}[],
|
|
513
|
+
* mode?: 'tool'|'text', catalogue?: Record<string, any>, writeScope?: string[]|null,
|
|
514
|
+
* jobLines?: {n: number}[]|null, inputs?: {n: number, label: string, value: string}[]|null, confirmed?: any,
|
|
515
|
+
* labelLists?: string[]|null,
|
|
516
|
+
* }} o
|
|
517
|
+
* @returns {string}
|
|
518
|
+
*/
|
|
519
|
+
export function rubricAuthorPrompt({ answers, questions, facts, listingBlock, verdictType, guards, mode, catalogue, writeScope, jobLines, inputs, confirmed, labelLists, }: {
|
|
520
|
+
answers: Record<string | number, string>;
|
|
521
|
+
questions?: Record<string | number, string>;
|
|
522
|
+
facts: any;
|
|
523
|
+
listingBlock: string;
|
|
524
|
+
verdictType: string;
|
|
525
|
+
guards: {
|
|
526
|
+
name: string;
|
|
527
|
+
kind: string;
|
|
528
|
+
params: Record<string, any>;
|
|
529
|
+
fill: string[];
|
|
530
|
+
}[];
|
|
531
|
+
mode?: "tool" | "text";
|
|
532
|
+
catalogue?: Record<string, any>;
|
|
533
|
+
writeScope?: string[] | null;
|
|
534
|
+
jobLines?: {
|
|
535
|
+
n: number;
|
|
536
|
+
}[] | null;
|
|
537
|
+
inputs?: {
|
|
538
|
+
n: number;
|
|
539
|
+
label: string;
|
|
540
|
+
value: string;
|
|
541
|
+
}[] | null;
|
|
542
|
+
confirmed?: any;
|
|
543
|
+
labelLists?: string[] | null;
|
|
483
544
|
}): string;
|
|
484
545
|
/**
|
|
485
546
|
* The seed read, as the model sees it: mechanical labels, no editorial. Per-stage
|
|
@@ -836,7 +897,8 @@ export function confirmPrompt({ answers, questions, labels, facts, listing, writ
|
|
|
836
897
|
* generate: Function, book: ReturnType<typeof makeCostBook>,
|
|
837
898
|
* ask: (step: {kind: string, [k: string]: any}) => Promise<string|null>,
|
|
838
899
|
* onPhase?: (phase: string, data?: any) => void, mode?: 'tool'|'text',
|
|
839
|
-
* worseThanBefore?: string, inputs?: {n: number, label: string, value: string}[]|null
|
|
900
|
+
* worseThanBefore?: string, inputs?: {n: number, label: string, value: string}[]|null,
|
|
901
|
+
* jobLines?: {n: number}[]|null}} o retired field (hamr's ruling 2026-09-28):
|
|
840
902
|
* `worseThanBefore` is never asked any more and defaults to `''` — a
|
|
841
903
|
* caller may still pass a string through (kept for a future, non-ask
|
|
842
904
|
* source of the same constraint), it just never comes from an ask() step.
|
|
@@ -847,7 +909,7 @@ export function confirmPrompt({ answers, questions, labels, facts, listing, writ
|
|
|
847
909
|
* worseThanBefore: string, openQuestions: string[], notChecked: string[], answeredQuestions?: string[]}|null,
|
|
848
910
|
* reds: Red[], cost: any}>}
|
|
849
911
|
*/
|
|
850
|
-
export function runConfirmTurn({ verdictType, answers, questions, labels, facts, listing, writeScope, isRepo, lang, generate, book, ask, onPhase, mode, worseThanBefore: presetWorseThanBefore, inputs, }: {
|
|
912
|
+
export function runConfirmTurn({ verdictType, answers, questions, labels, facts, listing, writeScope, isRepo, lang, generate, book, ask, onPhase, mode, worseThanBefore: presetWorseThanBefore, inputs, jobLines, }: {
|
|
851
913
|
verdictType: string;
|
|
852
914
|
answers: Record<string | number, string>;
|
|
853
915
|
questions: Record<string | number, string>;
|
|
@@ -875,6 +937,9 @@ export function runConfirmTurn({ verdictType, answers, questions, labels, facts,
|
|
|
875
937
|
label: string;
|
|
876
938
|
value: string;
|
|
877
939
|
}[] | null;
|
|
940
|
+
jobLines?: {
|
|
941
|
+
n: number;
|
|
942
|
+
}[] | null;
|
|
878
943
|
}): Promise<{
|
|
879
944
|
ok: boolean;
|
|
880
945
|
stop: null | "cap-halt" | "pricing-red" | "provider-red" | "artifact-red" | "confirm-abandoned" | "confirm-restart";
|
|
@@ -945,7 +1010,7 @@ export function runConfirmTurn({ verdictType, answers, questions, labels, facts,
|
|
|
945
1010
|
* offerQuestions?: boolean,
|
|
946
1011
|
* confirmed?: {checks: string[], checkItems?: {text: string, fromLine: number[]|null}[], protections: string[], openQuestions?: string[], notChecked?: string[], answeredQuestions?: string[]}|null}} o
|
|
947
1012
|
*/
|
|
948
|
-
export function authorClose({ workdir, seedRef, lang, verdictType, answers, questions, writeScope, scout, listing, priorCalls, priorRaws, confirmed, jobLines, inputs, generate, seedReadFn, closeCtx, ceilingUsd, onPhase, onCall, maxRevisions, structureRetries, structuredMode, catalogue, offerQuestions, }: {
|
|
1013
|
+
export function authorClose({ workdir, seedRef, lang, verdictType, answers, questions, writeScope, scout, listing, priorCalls, priorRaws, confirmed, jobLines, inputs, generate, seedReadFn, closeCtx, ceilingUsd, onPhase, onCall, maxRevisions, structureRetries, structuredMode, catalogue: catalogueIn, offerQuestions, }: {
|
|
949
1014
|
workdir: string;
|
|
950
1015
|
seedRef: string;
|
|
951
1016
|
lang: string;
|
|
@@ -1522,12 +1587,195 @@ export const PARAM_SCHEMAS: Readonly<{
|
|
|
1522
1587
|
};
|
|
1523
1588
|
description: string;
|
|
1524
1589
|
};
|
|
1590
|
+
step: {
|
|
1591
|
+
type: string;
|
|
1592
|
+
minimum: number;
|
|
1593
|
+
description: string;
|
|
1594
|
+
};
|
|
1595
|
+
parts: {
|
|
1596
|
+
type: string;
|
|
1597
|
+
description: string;
|
|
1598
|
+
items: {
|
|
1599
|
+
type: string;
|
|
1600
|
+
properties: {
|
|
1601
|
+
text: {
|
|
1602
|
+
type: string;
|
|
1603
|
+
minLength: number;
|
|
1604
|
+
description: string;
|
|
1605
|
+
};
|
|
1606
|
+
class: {
|
|
1607
|
+
enum: string[];
|
|
1608
|
+
};
|
|
1609
|
+
check: {
|
|
1610
|
+
enum: string[];
|
|
1611
|
+
description: string;
|
|
1612
|
+
};
|
|
1613
|
+
value: {
|
|
1614
|
+
type: string;
|
|
1615
|
+
minimum: number;
|
|
1616
|
+
description: string;
|
|
1617
|
+
};
|
|
1618
|
+
names: {
|
|
1619
|
+
type: string;
|
|
1620
|
+
minItems: number;
|
|
1621
|
+
items: {
|
|
1622
|
+
type: string;
|
|
1623
|
+
minLength: number;
|
|
1624
|
+
};
|
|
1625
|
+
description: string;
|
|
1626
|
+
};
|
|
1627
|
+
wordsPerSection: {
|
|
1628
|
+
type: string;
|
|
1629
|
+
minimum: number;
|
|
1630
|
+
};
|
|
1631
|
+
source: {
|
|
1632
|
+
type: string;
|
|
1633
|
+
minLength: number;
|
|
1634
|
+
description: string;
|
|
1635
|
+
};
|
|
1636
|
+
domains: {
|
|
1637
|
+
type: string;
|
|
1638
|
+
minItems: number;
|
|
1639
|
+
items: {
|
|
1640
|
+
type: string;
|
|
1641
|
+
minLength: number;
|
|
1642
|
+
};
|
|
1643
|
+
description: string;
|
|
1644
|
+
};
|
|
1645
|
+
covers: {
|
|
1646
|
+
type: string;
|
|
1647
|
+
minItems: number;
|
|
1648
|
+
uniqueItems: boolean;
|
|
1649
|
+
items: {
|
|
1650
|
+
type: string;
|
|
1651
|
+
minimum: number;
|
|
1652
|
+
};
|
|
1653
|
+
description: string;
|
|
1654
|
+
};
|
|
1655
|
+
rules: {
|
|
1656
|
+
type: string;
|
|
1657
|
+
minItems: number;
|
|
1658
|
+
uniqueItems: boolean;
|
|
1659
|
+
items: {
|
|
1660
|
+
type: string;
|
|
1661
|
+
minimum: number;
|
|
1662
|
+
};
|
|
1663
|
+
description: string;
|
|
1664
|
+
};
|
|
1665
|
+
facts: {
|
|
1666
|
+
type: string;
|
|
1667
|
+
minItems: number;
|
|
1668
|
+
maxItems: number;
|
|
1669
|
+
description: string;
|
|
1670
|
+
items: {
|
|
1671
|
+
type: string;
|
|
1672
|
+
properties: {
|
|
1673
|
+
name: {
|
|
1674
|
+
type: string;
|
|
1675
|
+
minLength: number;
|
|
1676
|
+
description: string;
|
|
1677
|
+
};
|
|
1678
|
+
shape: {
|
|
1679
|
+
enum: string[];
|
|
1680
|
+
description: string;
|
|
1681
|
+
};
|
|
1682
|
+
unit: {
|
|
1683
|
+
type: string;
|
|
1684
|
+
minLength: number;
|
|
1685
|
+
description: string;
|
|
1686
|
+
};
|
|
1687
|
+
currency: {
|
|
1688
|
+
enum: string[];
|
|
1689
|
+
description: string;
|
|
1690
|
+
};
|
|
1691
|
+
phrase: {
|
|
1692
|
+
type: string;
|
|
1693
|
+
minLength: number;
|
|
1694
|
+
description: string;
|
|
1695
|
+
};
|
|
1696
|
+
anchor: {
|
|
1697
|
+
type: string;
|
|
1698
|
+
minLength: number;
|
|
1699
|
+
description: string;
|
|
1700
|
+
};
|
|
1701
|
+
until: {
|
|
1702
|
+
type: string;
|
|
1703
|
+
minLength: number;
|
|
1704
|
+
description: string;
|
|
1705
|
+
};
|
|
1706
|
+
rule: {
|
|
1707
|
+
type: string;
|
|
1708
|
+
description: string;
|
|
1709
|
+
properties: {
|
|
1710
|
+
op: {
|
|
1711
|
+
enum: string[];
|
|
1712
|
+
};
|
|
1713
|
+
value: {
|
|
1714
|
+
type: string[];
|
|
1715
|
+
description: string;
|
|
1716
|
+
};
|
|
1717
|
+
lo: {
|
|
1718
|
+
type: string[];
|
|
1719
|
+
};
|
|
1720
|
+
hi: {
|
|
1721
|
+
type: string[];
|
|
1722
|
+
};
|
|
1723
|
+
};
|
|
1724
|
+
required: string[];
|
|
1725
|
+
additionalProperties: boolean;
|
|
1726
|
+
};
|
|
1727
|
+
aiProposal: {
|
|
1728
|
+
type: string;
|
|
1729
|
+
description: string;
|
|
1730
|
+
};
|
|
1731
|
+
};
|
|
1732
|
+
required: string[];
|
|
1733
|
+
additionalProperties: boolean;
|
|
1734
|
+
};
|
|
1735
|
+
};
|
|
1736
|
+
count: {
|
|
1737
|
+
type: string;
|
|
1738
|
+
description: string;
|
|
1739
|
+
properties: {
|
|
1740
|
+
min: {
|
|
1741
|
+
type: string;
|
|
1742
|
+
minimum: number;
|
|
1743
|
+
};
|
|
1744
|
+
max: {
|
|
1745
|
+
type: string;
|
|
1746
|
+
minimum: number;
|
|
1747
|
+
};
|
|
1748
|
+
};
|
|
1749
|
+
additionalProperties: boolean;
|
|
1750
|
+
};
|
|
1751
|
+
};
|
|
1752
|
+
required: string[];
|
|
1753
|
+
additionalProperties: boolean;
|
|
1754
|
+
};
|
|
1755
|
+
};
|
|
1525
1756
|
ask: {
|
|
1526
1757
|
type: string;
|
|
1527
1758
|
minLength: number;
|
|
1528
1759
|
description: string;
|
|
1529
1760
|
};
|
|
1530
1761
|
}>;
|
|
1762
|
+
/**
|
|
1763
|
+
* What a rubric job on local files means by DONE — the class statement of the text genre (Part 1a). It is stated as an order
|
|
1764
|
+
* about KINDS: every part of a job line is counted or checked against a quote, because nothing in this build judges.
|
|
1765
|
+
*/
|
|
1766
|
+
export const RUBRIC_FILES_STATEMENT: string;
|
|
1767
|
+
/**
|
|
1768
|
+
* THE RUBRIC RULES, in one place (live fix B1): what the confirm step and the drafter are both told about turning the person's words
|
|
1769
|
+
* into checks. The confirm note ({@link RUBRIC_CONFIRM_NOTE}) and the composer brief ({@link REQUIRED_FACTS_BRIEF}) interpolate THIS
|
|
1770
|
+
* constant, so the two cannot drift (in smv6k6l8dxkzs the confirm step planned a limit the drafter then could not carry, and the
|
|
1771
|
+
* drafter added an airport code the person never wrote). Field-neutral: neither turn needs the other's field names.
|
|
1772
|
+
*/
|
|
1773
|
+
export const RUBRIC_RULES: string;
|
|
1774
|
+
/**
|
|
1775
|
+
* REQUIRED FACTS, as the composer is told them (PART 1b, PRD item 33 RF1–RF17). The shape of the fact part is in the catalogue and the
|
|
1776
|
+
* schema; this is the judgement the schema cannot carry: what becomes a fact, where its words come from, and what is Not checked.
|
|
1777
|
+
*/
|
|
1778
|
+
export const REQUIRED_FACTS_BRIEF: string;
|
|
1531
1779
|
export const CONFIRM_TOOL_NAME: "confirm_plan";
|
|
1532
1780
|
/** Acknowledgement text the confirm tool's `execute` returns — mirrors
|
|
1533
1781
|
* {@link DECLARATION_ACK}'s role: an output channel that records and
|
|
@@ -1545,6 +1793,12 @@ export const CONFIRM_STRUCTURE_INSTRUCTION: string;
|
|
|
1545
1793
|
* an ORDER to the model itself, the same register {@link AUTHOR_SYSTEM} and
|
|
1546
1794
|
* {@link CLASS_STATEMENTS} already use, rather than a code matcher over prose.
|
|
1547
1795
|
*/
|
|
1796
|
+
/**
|
|
1797
|
+
* What the confirm turn tells the model about a rubric job on local text files (model-facing, registered). There is no
|
|
1798
|
+
* repository and no judge: a check is a limit a machine counts (words, lines, sections), or a claim a quote from a named
|
|
1799
|
+
* input must back. Everything else the person asked for is named in `notChecked`, in their words.
|
|
1800
|
+
*/
|
|
1801
|
+
export const RUBRIC_CONFIRM_NOTE: string;
|
|
1548
1802
|
export const CONFIRM_SYSTEM: string;
|
|
1549
1803
|
export type Red = {
|
|
1550
1804
|
code: string;
|