rcf-lite 0.30.0 → 0.32.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 +67 -0
- package/blueprints/deploy-cloudflare-workers/contributions/user-stories/deploy-cloudflare-workers-us-12113.json +1 -1
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/define-draft-shapes.md +15 -10
- package/package.json +1 -1
- package/rcf/adrs/adr-4131-gates-bite-ack-recorded-override.json +3 -3
- package/rcf/adrs/adr-4137-standalone-theme-persistence.json +30 -0
- package/rcf/adrs/adr-4138-tree-wide-checks-before-scope-shortcircuit.json +35 -0
- package/rcf/code-nodes/cn-017.json +4 -2
- package/rcf/code-nodes/cn-771.json +15 -0
- package/rcf/code-nodes/cn-772.json +14 -0
- package/rcf/code-nodes/cn-773.json +16 -0
- package/rcf/code-nodes/cn-774.json +20 -0
- package/rcf/code-nodes/cn-775.json +15 -0
- package/rcf/code-nodes/cn-776.json +17 -0
- package/rcf/code-nodes/cn-777.json +21 -0
- package/rcf/code-nodes/cn-778.json +14 -0
- package/rcf/fbs/fbs-004.json +4 -1
- package/rcf/fbs/fbs-201.json +2 -1
- package/rcf/fbs/fbs-202.json +18 -2
- package/rcf/fbs/fbs-203.json +12 -4
- package/rcf/requirements/req-002.json +2 -2
- package/rcf/requirements/req-173.json +2 -2
- package/rcf/requirements/req-174.json +2 -2
- package/rcf/tacs/tac-4122-define-gates.json +7 -2
- package/rcf/tacs/tac-4127-readiness-view.json +3 -2
- package/rcf/test-suites/ts-004.json +82 -2
- package/rcf/test-suites/ts-007.json +1 -1
- package/rcf/test-suites/ts-215.json +64 -1
- package/rcf/test-suites/ts-216.json +18 -2
- package/rcf/test-suites/ts-219.json +10 -2
- package/rcf/test-suites/ts-220.json +18 -2
- package/rcf/test-suites/ts-223.json +24 -2
- package/rcf/test-suites/ts-225.json +10 -2
- package/rcf/test-suites/ts-229.json +49 -3
- package/rcf/test-suites/ts-230.json +26 -2
- package/rcf/test-suites/ts-231.json +16 -1
- package/rcf/test-suites/ts-236.json +44 -0
- package/rcf/user-stories/us-17302.json +36 -1
- package/rcf/user-stories/us-17401.json +11 -1
- package/rcf/user-stories/us-17402.json +6 -1
- package/rcf/user-stories/us-17403.json +11 -1
- package/rcf/user-stories/us-17404.json +16 -1
- package/rcf/user-stories/us-17405.json +7 -2
- package/rcf/user-stories/us-18001.json +6 -1
- package/rcf/user-stories/us-202.json +25 -1
- package/rcf/user-stories/us-205.json +6 -1
- package/rcf/user-stories/us-206.json +25 -4
- package/rcf/user-stories/us-207.json +36 -0
- package/releases/releases.yaml +20 -1
- package/src/cli/create.js +22 -2
- package/src/cli/freeze.js +8 -0
- package/src/cli/ledger.js +96 -4
- package/src/cli/readiness.js +17 -39
- package/src/core/errors/index.js +7 -1
- package/src/core/store/writer.js +181 -13
- package/src/define/interface-paths.js +47 -0
- package/src/define/ledgers.js +86 -7
- package/src/mcp/tools.js +1 -1
- package/src/query/gates.js +431 -163
- package/src/view/html-page.js +43 -13
- package/src/view/live-client.js +18 -10
- package/src/view/page-init.js +149 -8
- package/src/view/style.css +107 -20
- package/src/view/view-index.js +22 -0
package/src/define/ledgers.js
CHANGED
|
@@ -102,7 +102,7 @@ export class LedgerError extends Error {
|
|
|
102
102
|
* @param {string} message
|
|
103
103
|
* @param {object} [opts]
|
|
104
104
|
* @param {string} [opts.filePath]
|
|
105
|
-
* @param {'parseFailure' | 'schemaFailure' | 'ioFailure' | 'usage'} [opts.code]
|
|
105
|
+
* @param {'parseFailure' | 'schemaFailure' | 'ioFailure' | 'usage' | 'conflict'} [opts.code]
|
|
106
106
|
* @param {string} [opts.field]
|
|
107
107
|
*/
|
|
108
108
|
constructor(message, opts = {}) {
|
|
@@ -390,26 +390,105 @@ export function nextIdFor(name, body) {
|
|
|
390
390
|
* updated body. Pure over its inputs (does not write to disk); the
|
|
391
391
|
* CLI calls `saveLedger` with the result.
|
|
392
392
|
*
|
|
393
|
+
* Ruling R1 2026-10-05 (w-2026-10-05-dave-001): `add` is idempotent by
|
|
394
|
+
* id. When `args.id` is set (the harness carries it on a replayed
|
|
395
|
+
* turn):
|
|
396
|
+
* - no entry with that id exists: honour the override and add the
|
|
397
|
+
* entry at that id (any gaps in the id sequence are legal; the
|
|
398
|
+
* per-ledger validator enforces uniqueness, not contiguity);
|
|
399
|
+
* - entry with that id exists and the content matches: return
|
|
400
|
+
* `{ status: 'unchanged', body, entry }`, no new write;
|
|
401
|
+
* - entry with that id exists and the content differs: throw a
|
|
402
|
+
* `LedgerError` (code 'conflict') naming the id and the differing
|
|
403
|
+
* field keys so the CLI exits 3. Update is a separate verb.
|
|
404
|
+
* When `args.id` is omitted the next id is minted as before (the
|
|
405
|
+
* pre-R1 behaviour; a replayed turn without a carried id still works
|
|
406
|
+
* but duplicates the entry at a new id, which is why the harness is
|
|
407
|
+
* responsible for threading --id back).
|
|
408
|
+
*
|
|
393
409
|
* @param {object} args
|
|
394
410
|
* @param {LedgerName} args.name
|
|
395
411
|
* @param {Record<string, unknown[]>} args.body
|
|
396
412
|
* @param {Record<string, unknown>} args.entry - fields other than id / addedAt / status
|
|
413
|
+
* @param {number} [args.id] - optional id override (R1)
|
|
397
414
|
* @param {string} [args.now] - ISO timestamp; defaults to Date.now()
|
|
398
|
-
* @returns {{ body: Record<string, unknown[]>, entry: Record<string, unknown
|
|
415
|
+
* @returns {{ body: Record<string, unknown[]>, entry: Record<string, unknown>, status?: 'added' | 'unchanged' }}
|
|
399
416
|
*/
|
|
400
|
-
export function addEntry({ name, body, entry, now }) {
|
|
417
|
+
export function addEntry({ name, body, entry, id, now }) {
|
|
401
418
|
const cfg = LEDGER_CONFIG[name];
|
|
402
419
|
if (!cfg) throw new LedgerError(`Unknown ledger '${name}'.`, { code: 'usage' });
|
|
403
|
-
const
|
|
420
|
+
const list = /** @type {any[]} */ (body[cfg.arrayKey] ?? []);
|
|
421
|
+
let resolvedId;
|
|
422
|
+
if (id !== undefined) {
|
|
423
|
+
if (!Number.isInteger(id) || id < 1) {
|
|
424
|
+
throw new LedgerError(`${name} add --id must be a positive integer, got ${id}.`, {
|
|
425
|
+
filePath: ledgerRelPath(name), field: 'id', code: 'usage',
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
const existing = list.find((e) => Number(e?.id) === Number(id));
|
|
429
|
+
if (existing) {
|
|
430
|
+
// R1 idempotency check: compare caller-supplied fields against the
|
|
431
|
+
// existing entry. `addedAt` and `status` default on first add, so
|
|
432
|
+
// they are compared only when the caller explicitly supplied them.
|
|
433
|
+
const differing = [];
|
|
434
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
435
|
+
if (value === undefined) continue;
|
|
436
|
+
if (key === 'id') continue;
|
|
437
|
+
if (key === 'addedAt' || key === 'status') {
|
|
438
|
+
// Default values auto-populated on first add; skip unless the
|
|
439
|
+
// caller passed them explicitly AND they differ.
|
|
440
|
+
if (existing[key] !== value) differing.push(key);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (!deepEqual(existing[key], value)) differing.push(key);
|
|
444
|
+
}
|
|
445
|
+
if (differing.length === 0) {
|
|
446
|
+
return { body, entry: existing, status: 'unchanged' };
|
|
447
|
+
}
|
|
448
|
+
throw new LedgerError(
|
|
449
|
+
`${name} ledger entry ${id} already exists with different content; differing fields: ${differing.sort().join(', ')}. Use the update verb to patch.`,
|
|
450
|
+
{ filePath: ledgerRelPath(name), field: `id ${id}`, code: 'conflict' },
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
resolvedId = id;
|
|
454
|
+
} else {
|
|
455
|
+
resolvedId = nextIdFor(name, body);
|
|
456
|
+
}
|
|
404
457
|
const full = {
|
|
405
|
-
id,
|
|
458
|
+
id: resolvedId,
|
|
406
459
|
...entry,
|
|
407
460
|
addedAt: entry.addedAt ?? now ?? new Date().toISOString(),
|
|
408
461
|
status: entry.status ?? 'open',
|
|
409
462
|
};
|
|
410
|
-
const nextBody = { ...body, [cfg.arrayKey]: [...
|
|
463
|
+
const nextBody = { ...body, [cfg.arrayKey]: [...list, full] };
|
|
411
464
|
validateLedger(name, nextBody, ledgerRelPath(name));
|
|
412
|
-
return { body: nextBody, entry: full };
|
|
465
|
+
return { body: nextBody, entry: full, status: 'added' };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Deep-equality over JSON-shaped values. Used by R1's idempotency
|
|
470
|
+
* check to compare a caller-supplied field against the existing entry.
|
|
471
|
+
* Arrays and plain objects are compared structurally; primitives via
|
|
472
|
+
* Object.is. Pure.
|
|
473
|
+
*/
|
|
474
|
+
function deepEqual(a, b) {
|
|
475
|
+
if (Object.is(a, b)) return true;
|
|
476
|
+
if (a === null || b === null) return false;
|
|
477
|
+
if (typeof a !== typeof b) return false;
|
|
478
|
+
if (Array.isArray(a)) {
|
|
479
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
480
|
+
for (let i = 0; i < a.length; i += 1) if (!deepEqual(a[i], b[i])) return false;
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
if (typeof a === 'object') {
|
|
484
|
+
if (typeof b !== 'object' || Array.isArray(b)) return false;
|
|
485
|
+
const ak = Object.keys(a);
|
|
486
|
+
const bk = Object.keys(b);
|
|
487
|
+
if (ak.length !== bk.length) return false;
|
|
488
|
+
for (const k of ak) if (!deepEqual(a[k], b[k])) return false;
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
413
492
|
}
|
|
414
493
|
|
|
415
494
|
/**
|
package/src/mcp/tools.js
CHANGED
|
@@ -639,7 +639,7 @@ const DEFINITIONS = [
|
|
|
639
639
|
properties: {
|
|
640
640
|
kind: { type: 'string', enum: KIND_ENUM, description: 'Document kind' },
|
|
641
641
|
parent: { type: 'string', description: 'Parent document id; required for every kind except cn (which has no parent)' },
|
|
642
|
-
id: { type: 'string', description: 'Override the auto-assigned id
|
|
642
|
+
id: { type: 'string', description: 'Override the auto-assigned id. Idempotent by id: an existing id with identical content is a no-op; different content refuses as a conflict naming the differing fields (use rcf_update to patch)' },
|
|
643
643
|
title: { type: 'string', description: 'Required for req / us / tac / adr / fbs / ts' },
|
|
644
644
|
description: { type: 'string', description: 'Required for ac / tc' },
|
|
645
645
|
purpose: { type: 'string', description: 'Required for ts' },
|