Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/CHECKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1260,15 +1260,15 @@ registrations by `registration.at`: before it they WARN under
| `RIG_REGISTRATION_BUSY` | another registration holds the project lock | finish that registration before retrying |
| `RIG_REGISTRATION_INVALID` | the version number or registration/source-kind record is malformed | restore the retained record and register the next numbered rig |
| `RIG_REGISTRATION_EXISTS` | a destination already holds frozen files | preserve those files and choose the next unused version |
| `RIG_REGISTRATION_PATH_INVALID` | an input escapes containment, traverses a symlink or collides with different bytes | retain regular source inputs with distinct contained destinations |
| `RIG_REGISTRATION_FILE_INVALID` | a required input, producer report/manifest, complete output inventory or copied totals is missing, changed or malformed | restore the exact frozen inputs and producer reports before registration |
| `RIG_REGISTRATION_PATH_INVALID` | an input escapes project containment, traverses a symlink or collides with reserved metadata, differing file bytes or an incompatible file/directory target | retain regular project-contained source inputs with literal targets and byte-identical shared files |
| `RIG_REGISTRATION_FILE_INVALID` | an unsupported adapter format, a required input, referenced asset, declared sha256, runtime config/requirements adaptation, producer report/manifest, complete output inventory or copied totals is missing, changed or malformed | restore the exact frozen inputs, retained source documents and producer reports before registration |
| `RIG_REGISTRATION_SOURCE_CHANGED` | source or project bytes change during copying | freeze the inputs and retry against the current project |
| `RIG_TOOLCHAIN_LOCK_INVALID` | the declared lock is absent, unreadable, differs from its hash, has lockfileVersion other than 3 or lacks either package version | retain the original npm v3 lock with both producer package entries |
| `RIG_TOOLCHAIN_MISMATCH` | built_with disagrees with the retained lock's two package versions | restore the declared lock and matching version values |
| `RIG_AT_SOURCE_INVALID` | the cited record/pointer lacks a valid UTC instant, differs from at or is later than registration | explicitly cite a retained dated record's UTC timestamp through --at-source and --at-pointer |
| `RIG_BUILD_BEFORE_REOPEN` | the cited not-after instant is at or before a reopen covering rig-review at registration | register a later frozen build with an explicit retained date citation |
| `RIG_PATH_ADAPTER_INVALID` | the adapter lacks distinct at/before/after mappings | retain the recorded adapter with unique config locations |
| `RIG_RUNTIME_PATH_UNRESOLVED` | an absolute config input lacks an exact adapter mapping, or a figure mask escapes the retained config directory | retain the recorded runtime mapping and contained named inputs |
| `RIG_PATH_ADAPTER_INVALID` | the adapter lacks distinct `{at,before,after}` or `{doc,pointer,before,after}` mappings, names an unknown document or non-asset pointer, or disagrees with `config-source.json` / `requirements-source.json` | retain an exact adapter matching the original `config` and `requirements` asset pointers and `before` values |
| `RIG_RUNTIME_PATH_UNRESOLVED` | an absolute config input lacks an exact adapter mapping, or a `requirements` asset path is absolute | retain the recorded runtime mapping and project-relative asset paths |
| `RIG_IDLE_MANIFEST_MISSING` | idle evidence lacks the public render's frames.json | render through public rigc render and supply its output directory |
| `RUN_RECORD_REQUIRED` | a `frozen-rig/1` version has no `registration.runs` (WARN before `RUN_RECORD_SINCE` by `registration.at`, refusal from `RUN_RECORD_SINCE` onward) | retain producer `run.json` records with `--run-record` and register a new rig version |
| `RUN_RECORD_SPEC_UNKNOWN` | a run record or `registration.runs[]` entry names a `spec` other than `rig-run/1` or `rigc-run/1`, or the `run.json` beside a draft's `rig.json` is not `rig-run/1` (`draft-rig` refuses; the check WARNs a recorded draft) | supply a producer `rig-run/1` or `rigc-run/1` `run.json` and register a new rig version |
Expand Down
30 changes: 23 additions & 7 deletions docs/PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -990,14 +990,30 @@ copying, and the copy is byte-verified before registration. The normal
same accepted-parts and config identity checks as its standalone command.

The retained original config and adapter are bound as `registration.config_source`
and `registration.path_adapter`. Each absolute config value requires an exact
and `registration.path_adapter`. Registrations using pointer adaptation declare
`registration.path_adapter.format` as `json-pointer/1`. Removing an adapter row
keeps that contract active. These registrations also bind the original
and runtime requirements as `registration.requirements_source` and
`registration.requirements`. Each absolute config value requires an exact
adapter row `{at:".section.field",before,after}` matching its runtime `after`
value. The writer copies the recorded `before` input and writes a contained
relative reference in the retained config. Config-named relative inputs and a
requirements-relative figure mask are retained too. Missing mappings, escaping
references, symlinks and colliding inputs refuse. Producer outputs retain their
original bytes. Packaging relocates the config's named inputs without rerunning
producer measurements.
value, and relative `config` or `requirements` asset references use explicit
`{doc:"config"|"requirements",pointer,before,after}` rows (recorded in the
retained `path-adapter.json`). The writer retains the original `config` and
`requirements` byte-identical, copies every referenced asset (`assemble.take`,
`assemble.editor_receipts`, `assemble.cuts`, `limbs`, `depth`,
`setup_painting.figure_mask` and `setup_painting.backdrop`) to literal
in-package paths (`no ..`, `no :`), verifies declared `sha256` digests before
copying, and checks that the runtime `config.json` and `requirements.json`
equal the retained sources after applying only the declared `path_adapter`
substitutions. Registrations without pointer adaptation retain their original
implicit take and editor-receipt relocation, permit unused legacy adapter rows
and do not require source-only relative assets or a backdrop that their writer
did not retain. Missing mappings, unlisted runtime differences, escaping
references, symlinks, hash mismatches and incompatible file or directory
collisions refuse. Identical file bytes may share a target, including an
explicit image inside a named plate directory. Producer
outputs retain their original bytes. Packaging relocates the config's and
requirements' named inputs without rerunning producer measurements.

Optional `--citations` reads an agent-authored map of the existing
`provenance.sections` shape. The writer retains that map and binds the adapted
Expand Down
19 changes: 17 additions & 2 deletions src/figure-mask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,23 @@ export function checkFigureMask(ctx: MaskContext): void {
finding('FIGURE_MASK_CHANGED', `${mask.path}: bytes differ from the declaration; restore the recorded PNG or update its declaration and derived record`);
return;
}
const original = resolve(dirname(resolve(ctx.dir, configPath)), requirements);
const originalMask = resolve(dirname(original), mask.path);
let original = resolve(dirname(resolve(ctx.dir, configPath)), requirements);
let maskRel = mask.path;
if (isObject(version.registration) && version.registration.kind === 'frozen-rig/1'
&& isObject(version.registration.path_adapter) && typeof version.registration.path_adapter.path === 'string') {
const reqSource = version.registration.requirements_source;
if (isObject(reqSource) && typeof reqSource.source_path === 'string') {
original = resolve(ctx.dir, reqSource.source_path);
}
try {
const rows = JSON.parse(readFileSync(resolve(ctx.dir, version.registration.path_adapter.path), 'utf8'));
if (Array.isArray(rows)) {
const maskRow = rows.find(r => isObject(r) && r.doc === 'requirements' && r.pointer === '/setup_painting/figure_mask/path' && typeof r.before === 'string' && r.after === mask.path);
if (maskRow) maskRel = maskRow.before as string;
}
} catch { /* Handled by registration checks. */ }
}
const originalMask = resolve(dirname(original), maskRel);
const recorded = asArray(ctx.project.derived).some((entry) => {
if (!isObject(entry) || entry.kind !== 'prepared-input' || typeof entry.path !== 'string'
|| resolve(ctx.dir, entry.path) !== originalMask || typeof entry.sha256 !== 'string' || entry.sha256.toLowerCase() !== mask.sha256
Expand Down
45 changes: 41 additions & 4 deletions src/review-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,13 @@ export function reviewConfigInputs(record: JsonObject, dir: string, n: number, m
if (existsSync(reqFile) && lstatSync(reqFile).isFile()) {
try {
const doc = JSON.parse(readFileSync(reqFile, 'utf8'));
const mask = isObject(doc) && isObject(doc.setup_painting) && isObject(doc.setup_painting.figure_mask) ? doc.setup_painting.figure_mask.path : undefined;
if (typeof mask === 'string' && mask.trim()) declared.add(relative(dirname(path), resolve(dirname(reqFile), mask)).split(sep).join('/'));
const setup = isObject(doc) && isObject(doc.setup_painting) ? doc.setup_painting : {};
for (const key of ['figure_mask', 'backdrop'] as const) {
const entry = isObject(setup[key]) ? setup[key] : undefined;
if (typeof entry?.path === 'string' && entry.path.trim()) {
declared.add(relative(dirname(path), resolve(dirname(reqFile), entry.path)).split(sep).join('/'));
}
}
} catch { /* Handled by figure-mask checks. */ }
}
}
Expand Down Expand Up @@ -355,10 +360,13 @@ function packageCommand(args: string[], candidate?: JsonObject, imagePaths?: rea
// loader's three config-file declarations. Preserve them on regeneration.
if (isObject(version.registration) && version.registration.kind === 'frozen-rig/1') {
const configDir = relative(dir, dirname(config)).split(sep).join('/');
const prefix = `${configDir}/inputs/`;
const prefix = `${configDir}/`;
const runFiles = new Set(asArray(version.registration.runs).flatMap(r => isObject(r) && isObject(r.source) && typeof r.source.path === 'string' ? [r.source.path] : []));
const metadataNames = new Set(['at-source.json', 'compile-report.json', 'config-source.json', 'requirements-source.json', 'config.json', 'package-lock.json', 'path-adapter.json', 'citations.json']);
for (const binding of asArray(version.registration.files)) {
if (!isObject(binding) || typeof binding.path !== 'string' || !binding.path.startsWith(prefix)) continue;
const rel = binding.path.slice(configDir.length + 1);
const rel = binding.path.slice(prefix.length);
if (metadataNames.has(rel) || runFiles.has(binding.path)) continue;
if ([...directories].some(d => rel.startsWith(`${d}/`)) && !named.some(input => input.path === rel)) continue;
const file = source(binding.path);
const bytes = readFileSync(file);
Expand Down Expand Up @@ -406,6 +414,35 @@ function packageCommand(args: string[], candidate?: JsonObject, imagePaths?: rea
if (existing && !existing.bytes.equals(bytes)) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${maskPath}: figure mask collides with a named input`);
if (!existing) named.push({ path: maskPath, source: maskFile, bytes });
}
const legacyUnadaptedReg = isObject(version.registration) && version.registration.kind === 'frozen-rig/1' && version.registration.requirements_source === undefined;
if (setup.backdrop !== undefined && !legacyUnadaptedReg) {
const backdrop = setup.backdrop;
if (!isObject(backdrop) || typeof backdrop.path !== 'string' || !backdrop.path.trim()
|| typeof backdrop.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(backdrop.sha256)) {
refuse('REVIEW_PACKAGE_INPUT_INVALID', `${requirements.path}: declare setup_painting.backdrop {path, sha256} before packaging`);
}
const backdropFile = resolve(dirname(config), dirname(requirements.path), backdrop.path);
const backdropPath = relative(dirname(config), backdropFile).split(sep).join('/');
if (isAbsolute(backdrop.path) || !backdropPath || backdropPath.split('/').some(part => part === '..' || part === '.' || part === '')) {
refuse('REVIEW_PACKAGE_PATH_INVALID', `${requirements.path}: backdrop ${backdrop.path} must stay inside the config directory`);
}
let current = dirname(config);
for (const part of backdropPath.split('/')) {
current = join(current, part);
if (existsSync(current) && lstatSync(current).isSymbolicLink()) refuse('REVIEW_PACKAGE_PATH_INVALID', `${backdropPath}: retain a backdrop without symlinks`);
}
let regularFile = false;
try { regularFile = lstatSync(backdropFile).isFile(); } catch { /* Missing paths are refused below. */ }
if (!regularFile) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${backdropPath}: restore the declared backdrop as a readable PNG file before packaging`);
let bytes: Buffer;
try { bytes = readFileSync(backdropFile); }
catch { refuse('REVIEW_PACKAGE_INPUT_INVALID', `${backdropPath}: cannot read the declared backdrop; restore its PNG before packaging`); }
if (hash(bytes) !== backdrop.sha256) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${backdropPath}: bytes differ from the declared sha256; restore the backdrop or record its corrected declaration before packaging`);
if (backdropPath === 'config.json' && !bytes.equals(configBytes)) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${backdropPath}: backdrop collides with the packaged config`);
const existing = named.find(input => input.path === backdropPath);
if (existing && !existing.bytes.equals(bytes)) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${backdropPath}: backdrop collides with a named input`);
if (!existing) named.push({ path: backdropPath, source: backdropFile, bytes });
}
}
}
// A compiled model already cited by an authored take cannot be rebound by packaging.
Expand Down
Loading
Loading