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
4 changes: 2 additions & 2 deletions MANIFEST.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ files:

- path: scripts/lib/obligation-first.js
role: tooling
hash: sha256:8ea2c5010e3ff91c3cda8e9398a40c4c2ac83baa712337b1b88250c32f11c041
hash: sha256:868310c404cbd0eab87e045541de95c23a2b9e67194495bc0efa130c1e3319fd
note: scripts/lib/obligation-first.js

- path: scripts/lib/temporal-status.js
Expand Down Expand Up @@ -1038,7 +1038,7 @@ files:

- path: tests/source-admission.test.js
role: code
hash: sha256:d2ec25e0a18c9c83b9ec505fedecf776f2b5971b4f4de24102273681467f45b2
hash: sha256:0d13f401800e2b95eea010905c6eea6c1c8081ed10d518dc8836931b5c4a6106
note: Source admission policy and producer-gate regressions

- path: tests/publication-state.test.js
Expand Down
4 changes: 2 additions & 2 deletions docs/MANIFEST.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ files:

- path: scripts/lib/obligation-first.js
role: tooling
hash: sha256:8ea2c5010e3ff91c3cda8e9398a40c4c2ac83baa712337b1b88250c32f11c041
hash: sha256:868310c404cbd0eab87e045541de95c23a2b9e67194495bc0efa130c1e3319fd
note: scripts/lib/obligation-first.js

- path: scripts/lib/temporal-status.js
Expand Down Expand Up @@ -1038,7 +1038,7 @@ files:

- path: tests/source-admission.test.js
role: code
hash: sha256:d2ec25e0a18c9c83b9ec505fedecf776f2b5971b4f4de24102273681467f45b2
hash: sha256:0d13f401800e2b95eea010905c6eea6c1c8081ed10d518dc8836931b5c4a6106
note: Source admission policy and producer-gate regressions

- path: tests/publication-state.test.js
Expand Down
6 changes: 3 additions & 3 deletions docs/api/v1/of/obligations.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
"anchors": [
"https://everyailaw.com/obligation-category/transparency.json"
],
"lifecycle_status": "unknown",
"operative_status": "unknown",
"enforcement_status": "unknown",
"lifecycle_status": "draft",
"operative_status": "future",
"enforcement_status": "unsignaled",
"jurisdiction": {
"@type": "of:Jurisdiction",
"territorial_scope": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
"anchors": [
"https://everyailaw.com/obligation-category/transparency.json"
],
"lifecycle_status": "unknown",
"operative_status": "unknown",
"enforcement_status": "unknown",
"lifecycle_status": "draft",
"operative_status": "future",
"enforcement_status": "unsignaled",
"jurisdiction": {
"@type": "of:Jurisdiction",
"territorial_scope": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
"anchors": [
"https://everyailaw.com/obligation-category/transparency.json"
],
"lifecycle_status": "unknown",
"operative_status": "unknown",
"enforcement_status": "unknown",
"lifecycle_status": "draft",
"operative_status": "future",
"enforcement_status": "unsignaled",
"jurisdiction": {
"@type": "of:Jurisdiction",
"territorial_scope": [
Expand Down
11 changes: 8 additions & 3 deletions scripts/lib/obligation-first.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,12 @@ function buildObligationRecords(config, data) {
const detail = provisionDetails.get(mapping.id);
const provision = detail ? detail.provision : {};
const recordId = concreteObligationId(mapping.id, obligationId);
const lifecycle = primary.lifecycle_status || provision.status || container?.status;
// A concrete obligation created only by a draft or proposed term is
// itself unissued: the term's state overrides the definition's
// generic lifecycle so a draft-derived duty never reads as operative.
const termStatus = provision.status || container?.status;
const draftCreated = ['draft', 'proposed'].includes(normalizeStatus(termStatus));
const lifecycle = draftCreated ? termStatus : primary.lifecycle_status || termStatus;
records.push(withEvidenceBoundary(compact({
'@context': recordContext(config),
'@type': obligationType(primary.group),
Expand All @@ -359,8 +364,8 @@ function buildObligationRecords(config, data) {
applicability: provision.scope ? [`scope:${provision.scope}`] : undefined,
anchors: sourceOwnedAnchors(mapping).obligationAnchors.length ? sourceOwnedAnchors(mapping).obligationAnchors : undefined,
lifecycle_status: normalizeStatus(lifecycle),
operative_status: primary.operative_status || container?.operative_status || operativeStatus(lifecycle, force),
enforcement_status: primary.enforcement_status || container?.enforcement_status || enforcementStatus({ status: lifecycle, type: container?.type }, force),
operative_status: (draftCreated ? undefined : primary.operative_status) || container?.operative_status || operativeStatus(lifecycle, force),
enforcement_status: (draftCreated ? undefined : primary.enforcement_status) || container?.enforcement_status || enforcementStatus({ status: lifecycle, type: container?.type }, force),
jurisdiction: typedJurisdiction(container?.jurisdiction),
...provenance(config, container?.official_url, provision.sections || mapping.source_heading, primary.last_verified || provision.verified || container?.last_verified, instrumentCitation(container || {})),
'pub:primary_id': obligationId,
Expand Down
12 changes: 6 additions & 6 deletions tests/fixtures/of-contract-fingerprint.json
Original file line number Diff line number Diff line change
Expand Up @@ -1894,9 +1894,9 @@
"present": 35
},
"enforcement_status": {
"explicit_unknown": 31,
"explicit_unknown": 30,
"missing": 0,
"populated": 4,
"populated": 5,
"present": 35
},
"evidence_type": {
Expand All @@ -1910,15 +1910,15 @@
"present": 35
},
"lifecycle_status": {
"explicit_unknown": 28,
"explicit_unknown": 27,
"missing": 0,
"populated": 7,
"populated": 8,
"present": 35
},
"operative_status": {
"explicit_unknown": 31,
"explicit_unknown": 30,
"missing": 0,
"populated": 4,
"populated": 5,
"present": 35
},
"projection_basis": {
Expand Down
29 changes: 29 additions & 0 deletions tests/source-admission.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,3 +421,32 @@ test('Obligation-First issuance determinations exclude original drafts even when
const ids = JSON.parse(fs.readFileSync(path.join(ROOT, 'docs/api/v1/of/determinations.json'), 'utf8')).determinations.map(record => record['pub:id']);
assert.equal(ids.includes('us-ut-oaip-jia-2026-001-issuance'), false);
});

test('an obligation created only by a draft or proposed term keeps the draft state', () => {
const config = { url: 'https://publedge.org/' };
const evidence = path => ({ kind: 'instrument', native_path: path, native_file_sha256: '1'.repeat(64), canonical_unit: null, canonical_sha256: '2'.repeat(64), admission_status: 'legacy-unreviewed', review_packet_sha256: null, retained_primary_sha256: null, unresolved_review_state: 'unknown', unresolved: null });
// The shared definition declares an operative lifecycle; only the creating term decides whether this concrete duty is issued.
const primary = { id: 'example-duty', name: 'Example duty', group: 'requirement', lifecycle_status: 'operative', operative_status: 'operative', enforcement_status: 'enforced', _body: '## Summary\n\nExample.\n', _evidence_input: { ...evidence('data/examples/obligations/example-duty.md'), kind: 'obligation-definition' } };
const project = container => {
const mapping = { id: 'example-mapping', regulation: container.id, obligations: ['example-duty'], source_heading: 'Summary', _evidence_input: { ...evidence('data/examples/mapping/index.yml'), kind: 'mapping-entry' } };
return obligationFirst.buildObligationFirstRecords(config, { containers: [container], primaries: [primary], authorities: [], mappingIndex: [mapping] }).obligations[0];
};
const base = { id: 'us-ut-oaip-jia-2099-001', type: 'jia', jurisdiction: 'us-ut', authority: 'utah-oaip', _evidence_input: evidence(JIA_FILE) };

for (const status of ['draft', 'proposed']) {
const duty = project({ ...base, status });
assert.equal(duty.lifecycle_status, status, `${status} creator must keep the duty ${status}`);
assert.notEqual(duty.operative_status, 'operative');
assert.notEqual(duty.enforcement_status, 'enforced');
}

// Negative control: an issued creator leaves the definition's own state in place.
const issued = project({ ...base, type: 'rma', status: 'enforcing' });
assert.ok(!['draft', 'proposed'].includes(issued.lifecycle_status));
assert.equal(issued.operative_status, 'operative');
assert.equal(issued.enforcement_status, 'enforced');

// The emitted JIA duty matches its draft term.
const emitted = JSON.parse(fs.readFileSync(path.join(ROOT, 'docs/api/v1/of/records/utah-mental-health-chatbot-disclosure-2026q2-first-session-disclose-genai-on-first-session.json'), 'utf8'));
assert.deepEqual([emitted.lifecycle_status, emitted.operative_status, emitted.enforcement_status], ['draft', 'future', 'unsignaled']);
});
Loading