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:868310c404cbd0eab87e045541de95c23a2b9e67194495bc0efa130c1e3319fd
hash: sha256:a29612a4039a612edbcfc0a55c70a6cb7668645b87a2b64b9d1d49cadb3a9b0d
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:0d13f401800e2b95eea010905c6eea6c1c8081ed10d518dc8836931b5c4a6106
hash: sha256:10be9419f4d32940c9988fed0e13edd48fdb0d81b456989b80794d89b6b0c163
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:868310c404cbd0eab87e045541de95c23a2b9e67194495bc0efa130c1e3319fd
hash: sha256:a29612a4039a612edbcfc0a55c70a6cb7668645b87a2b64b9d1d49cadb3a9b0d
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:0d13f401800e2b95eea010905c6eea6c1c8081ed10d518dc8836931b5c4a6106
hash: sha256:10be9419f4d32940c9988fed0e13edd48fdb0d81b456989b80794d89b6b0c163
note: Source admission policy and producer-gate regressions

- path: tests/publication-state.test.js
Expand Down
2 changes: 1 addition & 1 deletion docs/api/v1/of/instruments.json
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@
"territorial_scope": [
"us-ut"
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
4 changes: 2 additions & 2 deletions docs/api/v1/of/obligations.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
"us-ut"
]
},
"source": "https://commerce.utah.gov/ai/",
"source_locator": "Utah Code 搂13-72a-203",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"source_locator": "First-Session Disclosure (搂13-72a-203)",
"evidence_type": "reviewed-source-reference",
"verified": "2026-04-18",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
4 changes: 2 additions & 2 deletions docs/api/v1/of/parties.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"roles": [
"requesting_party"
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down Expand Up @@ -73,7 +73,7 @@
"roles": [
"interpreting_authority"
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"roles": [
"requesting_party"
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"roles": [
"interpreting_authority"
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
2 changes: 1 addition & 1 deletion docs/api/v1/of/records/us-ut-oaip-jia-2026-001.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"territorial_scope": [
"us-ut"
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
"us-ut"
]
},
"source": "https://commerce.utah.gov/ai/",
"source_locator": "Utah Code 搂13-72a-203",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"source_locator": "First-Session Disclosure (搂13-72a-203)",
"evidence_type": "reviewed-source-reference",
"verified": "2026-04-18",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
"us-ut"
]
},
"source": "https://commerce.utah.gov/ai/",
"source_locator": "Utah Code 搂13-72a-203",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"source_locator": "First-Session Disclosure (搂13-72a-203)",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
4 changes: 2 additions & 2 deletions docs/api/v1/of/terms.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
"us-ut"
]
},
"source": "https://commerce.utah.gov/ai/",
"source_locator": "Utah Code 搂13-72a-203",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"source_locator": "First-Session Disclosure (搂13-72a-203)",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
2 changes: 1 addition & 1 deletion docs/instrument/us-ut-oaip-jia-2026-001.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"territorial_scope": [
"us-ut"
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
"us-ut"
]
},
"source": "https://commerce.utah.gov/ai/",
"source_locator": "Utah Code 搂13-72a-203",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"source_locator": "First-Session Disclosure (搂13-72a-203)",
"evidence_type": "reviewed-source-reference",
"verified": "2026-04-18",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"roles": [
"requesting_party"
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
2 changes: 1 addition & 1 deletion docs/party/us-ut-oaip-jia-2026-001-utah-oaip.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"roles": [
"interpreting_authority"
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
"us-ut"
]
},
"source": "https://commerce.utah.gov/ai/",
"source_locator": "Utah Code 搂13-72a-203",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"source_locator": "First-Session Disclosure (搂13-72a-203)",
"evidence_type": "reviewed-source-reference",
"verified": "2026-09-23",
"asserted_by_adopter": "https://publedge.org/",
Expand Down
25 changes: 21 additions & 4 deletions scripts/lib/obligation-first.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,23 @@ function provenance(config, source, locator, verified, citation) {
});
}

// A PubLedge original draft is published by PubLedge, not by the authority it
// names. Its text's provenance is the draft record itself; the statute it
// interprets stays linked through source-owned anchors, and the locator names
// the draft's own section rather than a statutory section.
function isOriginalDraft(container) {
return container?.source === 'publedge-original-draft';
}

function recordSource(config, container) {
if (!isOriginalDraft(container)) return container?.official_url;
return container._canonicalPath ? `${siteBase(config)}/${container._canonicalPath}` : undefined;
}

function recordLocator(container, provision, mapping) {
return isOriginalDraft(container) ? mapping.source_heading : provision.sections || mapping.source_heading;
}

function partyId(container, party, index) {
return `${container.id}-${slugify(party.name || party.role || `party-${index + 1}`)}`;
}
Expand All @@ -219,7 +236,7 @@ function buildPartyRecords(config, data) {
party_kind: kind,
entity: kind === 'organization' ? { '@type': 'gist:Organization', name: party.name } : undefined,
roles: party.role ? [party.role] : undefined,
...provenance(config, container.official_url, undefined, container.last_verified, instrumentCitation(container))
...provenance(config, recordSource(config, container), undefined, container.last_verified, instrumentCitation(container))
}), [container._evidence_input]));
}
}
Expand Down Expand Up @@ -295,7 +312,7 @@ function buildInstrumentRecords(config, data, determinations) {
jurisdiction: typedJurisdiction(container.jurisdiction),
territorial_scope: container.jurisdiction ? [container.jurisdiction] : undefined,
citation: instrumentCitation(container),
...provenance(config, container.official_url, undefined, container.last_verified, instrumentCitation(container)),
...provenance(config, recordSource(config, container), undefined, container.last_verified, instrumentCitation(container)),
'pub:status': container.status,
'pub:editorial_status': container.editorial_status,
'pub:canonical_url': container._canonicalPath ? `${siteBase(config)}/${container._canonicalPath}` : undefined
Expand Down Expand Up @@ -329,7 +346,7 @@ function buildTermRecords(config, data) {
enforcement_status: container?.enforcement_status || enforcementStatus({ status: sourceStatus, type: container?.type }, force),
effective: /^\d{4}-\d{2}-\d{2}$/.test(provision.effective || '') ? provision.effective : undefined,
jurisdiction: typedJurisdiction(container?.jurisdiction),
...provenance(config, container?.official_url, provision.sections || mapping.source_heading, provision.verified || container?.last_verified, instrumentCitation(container || {})),
...provenance(config, recordSource(config, container), recordLocator(container, provision, mapping), provision.verified || container?.last_verified, instrumentCitation(container || {})),
'pub:source_heading': mapping.source_heading,
'pub:source_file': mapping.source_file
}), [container._evidence_input, mapping._evidence_input]);
Expand Down Expand Up @@ -367,7 +384,7 @@ function buildObligationRecords(config, data) {
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 || {})),
...provenance(config, recordSource(config, container), recordLocator(container, provision, mapping), primary.last_verified || provision.verified || container?.last_verified, instrumentCitation(container || {})),
'pub:primary_id': obligationId,
'pub:group': primary.group || undefined,
'pub:status': primary.status || undefined,
Expand Down
14 changes: 7 additions & 7 deletions tests/fixtures/of-contract-fingerprint.json
Original file line number Diff line number Diff line change
Expand Up @@ -3695,7 +3695,7 @@
"Lawyer review and authority sign-off remain pending; the draft is not issued.",
"The EveryAILaw amended-stack correspondence for the SB 149 slug was not compared field by field."
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"source_review_conflicts": null
},
"https://publedge.org/instrument/us-ut-oaip-rma-2024-001.json": {
Expand Down Expand Up @@ -7346,8 +7346,8 @@
"The obligation id and name still say 'first session'; renaming the id is a separate schema decision.",
"last_verified renewal requires a separate human review receipt."
],
"source": "https://commerce.utah.gov/ai/",
"source_locator": "Utah Code 搂13-72a-203",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"source_locator": "First-Session Disclosure (搂13-72a-203)",
"source_review_conflicts": null
},
"https://publedge.org/party/us-ut-oaip-jia-2026-001-publedge-illustrative-requesting-party.json": {
Expand Down Expand Up @@ -7380,7 +7380,7 @@
"Lawyer review and authority sign-off remain pending; the draft is not issued.",
"The EveryAILaw amended-stack correspondence for the SB 149 slug was not compared field by field."
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"source_review_conflicts": null
},
"https://publedge.org/party/us-ut-oaip-jia-2026-001-utah-oaip.json": {
Expand Down Expand Up @@ -7413,7 +7413,7 @@
"Lawyer review and authority sign-off remain pending; the draft is not issued.",
"The EveryAILaw amended-stack correspondence for the SB 149 slug was not compared field by field."
],
"source": "https://commerce.utah.gov/ai/",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"source_review_conflicts": null
},
"https://publedge.org/party/us-ut-oaip-rma-2024-001-elizachat-inc.json": {
Expand Down Expand Up @@ -8662,8 +8662,8 @@
"The sb149-learning-lab-and-ai-defense and hb452-mental-health-chatbot-stack mappings are kept and qualified, not removed; whether to remap them is a human decision.",
"The disclose-genai-on-first-session obligation id and name still say 'first session'; its text was corrected in a separate receipt."
],
"source": "https://commerce.utah.gov/ai/",
"source_locator": "Utah Code 搂13-72a-203",
"source": "https://publedge.org/us/utah/oaip/jia/2026-001/",
"source_locator": "First-Session Disclosure (搂13-72a-203)",
"source_review_conflicts": null
}
},
Expand Down
25 changes: 25 additions & 0 deletions tests/source-admission.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,28 @@ test('an obligation created only by a draft or proposed term keeps the draft sta
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']);
});

test('a PubLedge original draft cites itself as source and its own section as locator', () => {
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 });
const primary = { id: 'example-duty', name: 'Example duty', group: 'requirement', _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: 'Example Section (搂1-2-3)', _evidence_input: { ...evidence('data/examples/mapping/index.yml'), kind: 'mapping-entry' } };
const records = obligationFirst.buildObligationFirstRecords(config, { containers: [container], primaries: [primary], authorities: [], mappingIndex: [mapping] });
return { instrument: records.instruments[0], term: records.terms[0], duty: records.obligations[0] };
};
const base = { id: 'us-ut-oaip-jia-2099-001', type: 'jia', status: 'proposed', jurisdiction: 'us-ut', authority: 'utah-oaip', official_url: 'https://authority.example/ai/', _canonicalPath: 'us/utah/oaip/jia/2099-001/', parties: [{ name: 'Utah OAIP', role: 'interpreting_authority' }], _evidence_input: evidence(JIA_FILE) };

const draft = project({ ...base, source: 'publedge-original-draft' });
for (const record of [draft.instrument, draft.term, draft.duty]) assert.equal(record.source, 'https://publedge.org/us/utah/oaip/jia/2099-001/');
for (const record of [draft.term, draft.duty]) assert.equal(record.source_locator, 'Example Section (搂1-2-3)');

// Negative control: an authority-published record keeps the authority's source.
const remap = project({ ...base, source: 'demonstration-remap', status: 'enforcing' });
for (const record of [remap.instrument, remap.term, remap.duty]) assert.equal(record.source, 'https://authority.example/ai/');

const emitted = JSON.parse(fs.readFileSync(path.join(ROOT, 'docs/api/v1/of/records/utah-mental-health-chatbot-disclosure-2026q2-first-session.json'), 'utf8'));
assert.equal(emitted.source, 'https://publedge.org/us/utah/oaip/jia/2026-001/');
assert.equal(emitted.source_locator, 'First-Session Disclosure (搂13-72a-203)');
assert.deepEqual(emitted.anchors, ['https://everyailaw.com/term/utah-sb149-chatbot-disclosure.json'], 'the interpreted statute stays linked through the anchor');
});
Loading