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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ compatible, or editorial.

## Unreleased

- Clarified the unreleased Comic Manifest 1.0.0 candidate's (#95 prerequisite)
detached approval time bounds: decision time is inclusive, expiry is exclusive
at both action and scoped publication time. The test-only oracle preserves
submillisecond precision across variable fractional widths, accepts the full
schema timestamp separator forms, and validates trusted action calendar/clock
fields before comparison and fails closed on unsupported leap seconds. Synthetic
fixtures cover equal instants, expiry, future decisions and malformed action
times; no released contract bytes change.

- Extended the unreleased Comic Manifest 1.0.0 candidate for Studio #94 with
immutable versioned output profiles, per-output limits, exact requirement/result/
release metadata matching and explicit rights/accessibility/publication boundaries.
Expand Down
215 changes: 215 additions & 0 deletions fixtures/comic-manifest-v1-approval-times.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
[
{
"name": "approval remains valid at decision time",
"action_time": "2026-09-15T12:02:00Z",
"publication_time": "2026-09-15T12:10:00Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-15T12:11:00Z",
"valid": true
},
{
"name": "action just before expiry remains valid",
"action_time": "2026-09-15T12:10:00.000Z",
"publication_time": "2026-09-15T12:00:00Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-15T12:10:00.001Z",
"valid": true
},
{
"name": "publication just before expiry remains valid",
"action_time": "2026-09-15T12:05:00Z",
"publication_time": "2026-09-15T12:10:00Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-15T12:10:00.001Z",
"valid": true
},
{
"name": "action at expiry is expired",
"action_time": "2026-09-15T12:10:00Z",
"publication_time": "2026-09-15T12:09:59.999Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-15T12:10:00Z",
"valid": false
},
{
"name": "publication at expiry is expired",
"action_time": "2026-09-15T12:09:59.999Z",
"publication_time": "2026-09-15T12:10:00Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-15T12:10:00Z",
"valid": false
},
{
"name": "decision after action time is not yet valid",
"action_time": "2026-09-15T12:10:00Z",
"publication_time": "2026-09-15T12:10:00Z",
"decided_at": "2026-09-15T12:10:00.001Z",
"expires_at": "2026-09-15T12:11:00Z",
"valid": false
},
{
"name": "submillisecond action just before expiry remains valid",
"action_time": "2026-09-15T12:10:00.0000Z",
"publication_time": "2026-09-15T12:10:00.0000Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-15T12:10:00.0001Z",
"valid": true
},
{
"name": "submillisecond publication just before expiry remains valid",
"action_time": "2026-09-15T12:05:00Z",
"publication_time": "2026-09-15T12:10:00.00010Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-15T12:10:00.00011Z",
"valid": true
},
{
"name": "equivalent fractional widths preserve inclusive decision equality",
"action_time": "2026-09-15T12:02:00.0001000Z",
"publication_time": "2026-09-15T12:10:00.00010Z",
"decided_at": "2026-09-15T12:02:00.0001Z",
"expires_at": "2026-09-15T12:10:00.00011Z",
"valid": true
},
{
"name": "action at expiry is expired across fractional widths",
"action_time": "2026-09-15T12:10:00.0001000Z",
"publication_time": "2026-09-15T12:09:59.999Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-15T12:10:00.0001Z",
"valid": false
},
{
"name": "publication at expiry is expired across fractional widths",
"action_time": "2026-09-15T12:09:59.999999Z",
"publication_time": "2026-09-15T12:10:00.0001000Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-15T12:10:00.0001Z",
"valid": false
},
{
"name": "future submillisecond decision is rejected",
"action_time": "2026-09-15T12:10:00.0001Z",
"publication_time": "2026-09-15T12:10:00.0009Z",
"decided_at": "2026-09-15T12:10:00.0009Z",
"expires_at": "2026-09-15T12:10:00.0010Z",
"valid": false
},
{
"name": "equivalent instants across accepted timestamp separators remain valid",
"action_time": "2026-09-15t12:10:00.0001000Z",
"schema_valid_action_time": true,
"publication_time": "2026-09-15\t12:10:00.00010Z",
"decided_at": "2026-09-15 12:10:00.0001Z",
"expires_at": "2026-09-15T12:10:00.00011Z",
"valid": true
},
{
"name": "valid leap-day action time remains valid",
"action_time": "2024-02-29t12:10:00Z",
"schema_valid_action_time": true,
"publication_time": "2024-02-29 12:10:00Z",
"decided_at": "2024-02-29T12:02:00Z",
"expires_at": "2024-02-29T12:11:00Z",
"valid": true
},
{
"name": "last ordinary second with maximum schema precision remains valid",
"action_time": "2026-09-15T23:59:59.99999999999Z",
"schema_valid_action_time": true,
"publication_time": "2026-09-15 23:59:59.99999999999Z",
"decided_at": "2026-09-15T23:59:59.99999999998Z",
"expires_at": "2026-09-16T00:00:00Z",
"valid": true
},
{
"name": "action at expiry is expired across accepted timestamp separators",
"action_time": "2026-09-15 12:10:00.0001000Z",
"schema_valid_action_time": true,
"publication_time": "2026-09-15T12:09:59.999Z",
"decided_at": "2026-09-15t12:02:00Z",
"expires_at": "2026-09-15t12:10:00.0001Z",
"valid": false
},
{
"name": "publication at expiry is expired across accepted timestamp separators",
"action_time": "2026-09-15T12:09:59.999999Z",
"publication_time": "2026-09-15t12:10:00.0001000Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-15 12:10:00.0001Z",
"valid": false
},
{
"name": "invalid trusted action hour is rejected",
"action_time": "2026-09-15T24:00:00Z",
"publication_time": "2026-09-15T12:10:00Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-16T00:00:00Z",
"valid": false
},
{
"name": "invalid trusted action minute is rejected",
"action_time": "2026-09-15T12:60:00Z",
"publication_time": "2026-09-15T12:10:00Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-16T00:00:00Z",
"valid": false
},
{
"name": "invalid trusted action second is rejected",
"action_time": "2026-09-15T12:03:99Z",
"publication_time": "2026-09-15T12:10:00Z",
"decided_at": "2026-09-15T12:02:00Z",
"expires_at": "2026-09-16T00:00:00Z",
"valid": false
},
{
"name": "invalid trusted action calendar date is rejected",
"action_time": "2026-02-30T12:10:00Z",
"publication_time": "2026-02-28T12:10:00Z",
"decided_at": "2026-02-28T12:02:00Z",
"expires_at": "2026-03-01T00:00:00Z",
"valid": false
},
{
"name": "invalid non-leap-year February 29 action date is rejected",
"action_time": "2025-02-29T12:10:00Z",
"publication_time": "2025-02-28T12:10:00Z",
"decided_at": "2025-02-28T12:02:00Z",
"expires_at": "2025-03-01T00:00:00Z",
"valid": false
},
{
"name": "schema-valid midmonth leap-second action time is unsupported",
"action_time": "2026-09-15T23:59:60Z",
"schema_valid_action_time": true,
"publication_time": "2026-09-15T23:59:59Z",
"decided_at": "2026-09-15T23:59:58Z",
"expires_at": "2026-09-16T00:00:00Z",
"valid": false
},
{
"name": "schema-valid leap-second decision cannot authorize",
"action_time": "2026-09-16T00:00:00Z",
"publication_time": "2026-09-15T23:59:59Z",
"decided_at": "2026-09-15T23:59:60Z",
"expires_at": "2026-09-16T00:01:00Z",
"valid": false
},
{
"name": "schema-valid leap-second expiry cannot grant authority",
"action_time": "2026-09-15T23:59:59Z",
"publication_time": "2026-09-15T23:59:59Z",
"decided_at": "2026-09-15T23:59:58Z",
"expires_at": "2026-09-15T23:59:60Z",
"valid": false
},
{
"name": "schema-valid leap-second publication time cannot authorize",
"action_time": "2026-09-15T23:59:59Z",
"publication_time": "2026-09-15T23:59:60Z",
"decided_at": "2026-09-15T23:59:58Z",
"expires_at": "2026-09-16T00:01:00Z",
"valid": false
}
]
13 changes: 11 additions & 2 deletions fixtures/comic-manifest-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,17 @@ correction, partial/failed evidence, deterministic identity, exact versioned out
profiles, inclusive pixel/byte boundaries, profile/media incompatibility, missing,
duplicate and undeclared outputs, accessibility/rights metadata mismatch, unknown
versions/fields, ordered identities, immutable references, package bindings,
stale/revoked/forged approvals, public gates and private-context projection. Tests
compose non-rendered image/PDF placeholder bytes to exercise declared profile
stale/revoked/forged approvals, approval-time boundaries, public gates and
private-context projection. The [approval-time fixture matrix](comic-manifest-v1-approval-times.json)
covers inclusive decision equality, just-before and equal expiry at both action
and publication time, future decisions, submillisecond boundaries, equal
instants with different fractional widths, and equivalent instants using the
uppercase `T`, lowercase `t`, or a single whitespace separator accepted by the
schema. Trusted action-time cases cover invalid hours, minutes, seconds and
calendar dates, valid leap-day and maximum-precision ordinary-second controls,
and fail-closed behavior for schema-valid second-60 timestamps in action,
decision, expiry and publication fields.
The tests compose non-rendered image/PDF placeholder bytes to exercise declared profile
relationships; they deliberately do not claim media signatures or rendering.
Output text bytes and public dependency bytes are explicit fixture inputs and
independently hashed. Tests never create an approval from a production runtime or
Expand Down
14 changes: 14 additions & 0 deletions rfcs/0007-comic-manifest-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ approval bindings avoid circular hashes and preserve exact scope without grantin
authority from schema validity. Public private-context lineage uses approved random
attestations and protected mappings, not protected record IDs or hashes.

Trusted approval-time evaluation uses an inclusive decision-time bound and an
exclusive expiry bound: `decided_at <= action_time < expires_at`. The publication
time named in the exact scope must also be strictly earlier than `expires_at`.
Equality with `decided_at` is valid; equality with `expires_at` at either the
action or scoped publication time is expired. Before comparison, a verifier must
validate trusted `action_time` against the candidate schema's `$defs/time`
constraints, including calendar and clock ranges; invalid values must fail closed.
Approval-time comparisons support seconds `00` through `59`; a timestamp with
second `60` must fail closed for this check, even if its shape passes JSON Schema
date-time format validation. This deliberately does not infer leap-second dates.
Valid `T`, `t`, or single-whitespace separators and fractional seconds remain
accepted as defined by that schema. This records the existing test-oracle
relationship as an explicit contract rule.

Codex owns normative meaning. Platform validates and supplies a test-only consumer
proof against the existing released SDK/Builder output. Universe owns public
publication and canon; private source owners retain access/disclosure authority.
Expand Down
19 changes: 17 additions & 2 deletions specs/manifests/comic-manifest-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,23 @@ raw artifact digests (empty for production review), exact release `scope`, revie
role/actor, decision UUID, UTC decision/expiry and external authority reference.
A binding is necessary evidence, not an authenticated grant. Trusted verification
MUST bind the actual reviewer to the role, subject, scope and decision, check
revocation and current time, and fail closed on missing evidence. It MUST be valid
at the actual action and authorized publication time. Never trust an allow boolean.
revocation and current time, and fail closed on missing evidence. Let `action_time`
be the trusted time at which the authorization-dependent action is evaluated. The
verifier MUST validate `action_time` against the candidate schema's `$defs/time`
constraints, including calendar and clock ranges, before comparison. Invalid
calendar dates and out-of-range clock fields MUST fail closed. Approval-time
comparisons support seconds `00` through `59`; a timestamp with second `60` MUST
fail closed for this check, even if its shape passes JSON Schema date-time format
validation. This deliberately does not infer leap-second dates. Valid `T`, `t`, or
single-whitespace separators and fractional seconds remain accepted as defined by
the schema. The time check MUST satisfy `decided_at <= action_time < expires_at`; it MUST also
satisfy `scope.publication_time < expires_at` for the publication time named in the
exact scope. Expiry is exclusive: an action or scoped publication at exactly
`expires_at` is expired, while an action at exactly `decided_at` is valid. A
decision later than `action_time` is not yet valid. The publication-time check
continues to use the existing scope relationship; this clarification adds no
separate ordering rule between `decided_at` and `scope.publication_time`. Never
trust an allow boolean.

The production-reviewer approves the exact production intent for its declared
scope. Publisher and canon-editor decisions bind the exact public release payload
Expand Down
18 changes: 18 additions & 0 deletions tests/comic-manifest.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const read = path => JSON.parse(readFileSync(resolve(__dirname, '..', path)));
const schema = read('schemas/json/comic-manifest/v1/comic-manifest.schema.json');
const ajv = new Ajv({ strict: true, strictRequired: false, strictTypes: false, allErrors: true }); addFormats(ajv);
const validate = ajv.compile(schema), approval = ajv.compile({ $ref: schema.$id + '#/$defs/approval' });
const timestamp = ajv.compile(schema.$defs.time);
const base = read('fixtures/comic-manifest-v1.json'), foundation = read('fixtures/context-builder-v1.json');
const approvalTimeCases = read('fixtures/comic-manifest-v1-approval-times.json');
function schemaValid(s) {
for (const k of ['production','result','release']) assert.equal(validate(s[k]), true, `${k}: ${JSON.stringify(validate.errors)}`);
for (const a of s.approvals) assert.equal(approval(a), true, JSON.stringify(approval.errors));
Expand Down Expand Up @@ -73,6 +75,22 @@ test('all detached approval fields are closed and exactly versioned', () => {
assert.equal(approval({...base.approvals[0],allow:true}),false);
assert.equal(approval({...base.approvals[0],spec_version:'2.0.0'}),false);
});
for (const c of approvalTimeCases) test(c.name, () => {
const s = structuredClone(base);
s.at = c.action_time;
if (c.schema_valid_action_time !== undefined) assert.equal(timestamp(s.at), c.schema_valid_action_time);
s.release.scope.publication_time = c.publication_time;
for (const a of s.approvals) {
a.decided_at = c.decided_at;
a.expires_at = c.expires_at;
}
s.release.approvers = s.approvals.filter(a => a.role !== 'production-reviewer')
.map(({ decision_id, role, actor, decided_at }) => ({ decision_id, role, actor, decided_at }));
rebind(s);
schemaValid(s);
if (c.valid) verifyScenario(s, foundation);
else assert.throws(() => verifyScenario(s, foundation), /APPROVAL_TIME/);
});
for (const c of read('fixtures/comic-manifest-v1-cases.json')) test(c.name, () => {
const s = structuredClone(base);
for (const edit of c.edits) {
Expand Down
29 changes: 27 additions & 2 deletions tests/support/comic-manifest-oracle.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ const check = (v, code) => assert.ok(v, code);
const unique = (xs, code) => eq(new Set(xs).size, xs.length, code);
const rank = c => ['public', 'internal', 'confidential', 'restricted'].indexOf(c);
const gates = ['editorial', 'canon-continuity', 'visual-text', 'integrity', 'provenance', 'security-privacy', 'rights', 'accessibility', 'packaging'];
function parseUtcInstant(value) {
if (typeof value !== 'string' || value.length > 32) return null;
const match = /^(\d{4})-(\d{2})-(\d{2})[Tt\s](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?Z$/.exec(value);
if (!match) return null;
const [, yearText, monthText, dayText, hourText, minuteText, secondText, fraction] = match;
const year = Number(yearText), month = Number(monthText), day = Number(dayText);
const hour = Number(hourText), minute = Number(minuteText), second = Number(secondText);
const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
const days = [0, 31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if (month < 1 || month > 12 || day < 1 || day > days[month]) return null;
if (hour > 23 || minute > 59 || second > 59) return null;
return {
wholeSecond: `${yearText}-${monthText}-${dayText}T${hourText}:${minuteText}:${secondText}`,
fraction: fraction ?? ''
};
}
function compareUtcInstants(left, right) {
const a = parseUtcInstant(left), b = parseUtcInstant(right);
check(a && b, 'APPROVAL_TIME');
if (a.wholeSecond !== b.wholeSecond) return a.wholeSecond < b.wholeSecond ? -1 : 1;
const width = Math.max(a.fraction.length, b.fraction.length);
const af = a.fraction.padEnd(width, '0'), bf = b.fraction.padEnd(width, '0');
return af < bf ? -1 : af > bf ? 1 : 0;
}
const renditionProfiles = {
'comic-page-image@1.0.0': { media: ['image/png', 'image/jpeg', 'image/webp'], maxBytes: 50_000_000, image: true },
'comic-portable-document@1.0.0': { media: ['application/pdf'], maxBytes: 50_000_000, image: false },
Expand Down Expand Up @@ -96,8 +120,9 @@ function verifyApproval(a, subject, artifacts, scope, s) {
eq(a.subject, identity(subject), 'APPROVAL_SUBJECT');
eq(a.artifact_digests, artifacts.map(x => x.artifact.sha256).sort(), 'APPROVAL_ARTIFACTS');
eq(a.scope, scope, 'APPROVAL_SCOPE');
const at = Date.parse(s.at);
check(Date.parse(a.decided_at) <= at && at < Date.parse(a.expires_at) && Date.parse(scope.publication_time) < Date.parse(a.expires_at), 'APPROVAL_TIME');
// Validate trusted action time before comparing arbitrary schema-valid fractions.
check(compareUtcInstants(a.decided_at, s.at) <= 0 && compareUtcInstants(s.at, a.expires_at) < 0 &&
compareUtcInstants(scope.publication_time, a.expires_at) < 0, 'APPROVAL_TIME');
// A test harness supplies trusted decisions OUTSIDE untrusted record bytes.
const trusted = s.trust.approvals.find(x => x.decision_id === a.decision_id);
check(trusted && trusted.sha256 === identity(a).sha256 && !s.trust.revoked.includes(a.decision_id), 'APPROVAL_TRUST');
Expand Down
Loading