From 6260960f2909d34803dad2890f0c0bfd0f7bede7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 09:35:36 +0900 Subject: [PATCH 01/68] test(document-records): require durable retry idempotency --- ...st_document_record_idempotency_postgres.sh | 233 ++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 tests/test_document_record_idempotency_postgres.sh diff --git a/tests/test_document_record_idempotency_postgres.sh b/tests/test_document_record_idempotency_postgres.sh new file mode 100644 index 000000000..c333446f3 --- /dev/null +++ b/tests/test_document_record_idempotency_postgres.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" + +for migration in \ + database/migrations/0001_foundation_schema.sql \ + database/migrations/0002_sealed_evidence_digest.sql \ + database/migrations/0021_document_record_persistence.sql \ + database/migrations/0022_document_record_evidence_unique_keys.sql \ + database/migrations/0023_document_record_canonical_encoding.sql \ + database/migrations/0024_document_record_idempotent_persistence.sql; do + if [[ ! -f "${migration}" ]]; then + echo "required document-record idempotency migration is missing: ${migration}" >&2 + exit 1 + fi + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f "${migration}" +done + +TENANT_ID="10000000-0000-7000-8000-000000000001" +OTHER_TENANT_ID="20000000-0000-7000-8000-000000000002" +PERSON_REFERENCE="person_record:00000000-0000-4000-8000-000000000011" +EMPLOYMENT_REFERENCE="employment_record:00000000-0000-4000-8000-000000000021" +UPLOADER="actor:00000000-0000-4000-8000-000000000061" +PERSISTED_BY="actor:00000000-0000-4000-8000-000000000062" +RETENTION_REFERENCE="retention_policy:00000000-0000-4000-8000-000000000051" +ARTIFACT_DIGEST="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +SOURCE_DIGEST="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +RETENTION_DIGEST="cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" +APPLICATION_DIGEST="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" +CONFLICTING_APPLICATION_DIGEST="ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +IDEMPOTENCY_KEY="document-record-persist-00000000-0000-4000-8000-000000000101" +CONCURRENT_KEY="document-record-persist-00000000-0000-4000-8000-000000000102" + +IFS='|' read -r RECEIVED_AT EVIDENCE_RECORDED_AT < <(psql "${DATABASE_URL}" -Atqc " +SELECT + to_char((pg_catalog.transaction_timestamp() - interval '2 minutes') AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"'), + to_char((pg_catalog.transaction_timestamp() - interval '1 minute') AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"'); +") + +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <&2 + exit 1 +fi + +counts="$(psql "${DATABASE_URL}" -Atqc " +SELECT + (SELECT count(*) FROM document_record WHERE tenant_record_id = '${TENANT_ID}'::uuid AND document_record_id = '${DOCUMENT_ID}'::uuid)::text + || '|' || + (SELECT count(*) FROM document_record_persist_receipt WHERE tenant_record_id = '${TENANT_ID}'::uuid AND idempotency_key = '${IDEMPOTENCY_KEY}')::text; +")" +if [[ "${counts}" != "1|1" ]]; then + echo "same semantic retry duplicated durable document or receipt state: ${counts}" >&2 + exit 1 +fi + +set +e +conflict_output="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" \ + -c "$(persist_sql "${IDEMPOTENCY_KEY}" "${DOCUMENT_ID}" "${DOCUMENT_REFERENCE}" "${ARTIFACT_REFERENCE}" "${AUDIT_REFERENCE}" "${OUTBOX_REFERENCE}" "${CONFLICTING_APPLICATION_DIGEST}" "${CANONICAL_EVIDENCE}" "${EVIDENCE_DIGEST}")" 2>&1)" +conflict_status=$? +set -e +if [[ ${conflict_status} -eq 0 || "${conflict_output}" != *"idempotency key is bound to a different document persistence command"* ]]; then + echo "same idempotency key accepted a different semantic command: ${conflict_output}" >&2 + exit 1 +fi + +CONCURRENT_DOCUMENT_ID="00000000-0000-7000-8000-000000000132" +CONCURRENT_DOCUMENT_REFERENCE="document_record:00000000-0000-4000-8000-000000000132" +CONCURRENT_ARTIFACT_REFERENCE="document_artifact:00000000-0000-4000-8000-000000000142" +CONCURRENT_AUDIT_REFERENCE="audit_event:00000000-0000-4000-8000-000000000173" +CONCURRENT_OUTBOX_REFERENCE="outbox_event:00000000-0000-4000-8000-000000000174" +mapfile -t concurrent_evidence_parts < <(build_evidence "${CONCURRENT_DOCUMENT_REFERENCE}" "${CONCURRENT_ARTIFACT_REFERENCE}") +CONCURRENT_EVIDENCE="${concurrent_evidence_parts[0]}" +CONCURRENT_EVIDENCE_DIGEST="${concurrent_evidence_parts[1]}" +CONCURRENT_SQL="$(persist_sql "${CONCURRENT_KEY}" "${CONCURRENT_DOCUMENT_ID}" "${CONCURRENT_DOCUMENT_REFERENCE}" "${CONCURRENT_ARTIFACT_REFERENCE}" "${CONCURRENT_AUDIT_REFERENCE}" "${CONCURRENT_OUTBOX_REFERENCE}" "${APPLICATION_DIGEST}" "${CONCURRENT_EVIDENCE}" "${CONCURRENT_EVIDENCE_DIGEST}")" +FIRST_OUTPUT="$(mktemp)" +SECOND_OUTPUT="$(mktemp)" +cleanup() { rm -f "${FIRST_OUTPUT}" "${SECOND_OUTPUT}"; } +trap cleanup EXIT + +PGAPPNAME=orgmetra_document_idempotency_first psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 \ + -v canonical_evidence="${CONCURRENT_EVIDENCE}" >"${FIRST_OUTPUT}" <"${SECOND_OUTPUT}" & +second_pid=$! +wait "${first_pid}" +wait "${second_pid}" +first_concurrent_result="$(grep -F 'document_record:' "${FIRST_OUTPUT}" | head -n 1)" +second_concurrent_result="$(grep -F 'document_record:' "${SECOND_OUTPUT}" | head -n 1)" +if [[ -z "${first_concurrent_result}" || "${first_concurrent_result}" != "${second_concurrent_result}" ]]; then + echo "concurrent same-semantic attempts did not converge: first=${first_concurrent_result} second=${second_concurrent_result}" >&2 + exit 1 +fi + +concurrent_counts="$(psql "${DATABASE_URL}" -Atqc " +SELECT + (SELECT count(*) FROM document_record WHERE tenant_record_id = '${TENANT_ID}'::uuid AND document_record_id = '${CONCURRENT_DOCUMENT_ID}'::uuid)::text + || '|' || + (SELECT count(*) FROM document_record_persist_receipt WHERE tenant_record_id = '${TENANT_ID}'::uuid AND idempotency_key = '${CONCURRENT_KEY}')::text; +")" +if [[ "${concurrent_counts}" != "1|1" ]]; then + echo "concurrent retry duplicated durable state: ${concurrent_counts}" >&2 + exit 1 +fi + +active_test_connections="$(psql "${DATABASE_URL}" -Atqc " +SELECT count(*) FROM pg_stat_activity +WHERE application_name IN ('orgmetra_document_idempotency_first', 'orgmetra_document_idempotency_second'); +")" +if [[ "${active_test_connections}" != "0" ]]; then + echo "idempotency acceptance leaked PostgreSQL connections: ${active_test_connections}" >&2 + exit 1 +fi + +rls_state="$(psql "${DATABASE_URL}" -Atqc " +SELECT relrowsecurity::text || '|' || relforcerowsecurity::text +FROM pg_class WHERE oid = 'document_record_persist_receipt'::regclass; +")" +if [[ "${rls_state}" != "true|true" ]]; then + echo "document-record idempotency receipt is not FORCE-RLS protected: ${rls_state}" >&2 + exit 1 +fi + +set +e +mutation_output="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -c " +UPDATE document_record_persist_receipt +SET semantic_command_digest_sha256 = '${CONFLICTING_APPLICATION_DIGEST}' +WHERE tenant_record_id = '${TENANT_ID}'::uuid AND idempotency_key = '${IDEMPOTENCY_KEY}';" 2>&1)" +mutation_status=$? +set -e +if [[ ${mutation_status} -eq 0 || "${mutation_output}" != *"append-only"* ]]; then + echo "document-record idempotency receipt was mutable: ${mutation_output}" >&2 + exit 1 +fi From b341784aaabca61dff2663986d24fd5e61b5a1c9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 09:36:41 +0900 Subject: [PATCH 02/68] feat(document-records): make persistence retries idempotent --- ...document_record_idempotent_persistence.sql | 320 ++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 database/migrations/0024_document_record_idempotent_persistence.sql diff --git a/database/migrations/0024_document_record_idempotent_persistence.sql b/database/migrations/0024_document_record_idempotent_persistence.sql new file mode 100644 index 000000000..76d49c765 --- /dev/null +++ b/database/migrations/0024_document_record_idempotent_persistence.sql @@ -0,0 +1,320 @@ +-- Bind one purpose-scoped idempotency key to one semantic document persistence +-- command and its first committed result. The advisory lock covers only the +-- database transaction that checks replay state and writes the immutable fact; +-- no external computation or network work occurs while it is held. + +BEGIN; + +SET LOCAL search_path = public, pg_catalog; + +ALTER TABLE document_record + ADD CONSTRAINT document_record_tenant_identity_unique + UNIQUE (tenant_record_id, document_record_id); + +CREATE TABLE document_record_persist_receipt ( + tenant_record_id uuid NOT NULL REFERENCES tenant_record(tenant_record_id), + idempotency_key text NOT NULL, + semantic_command_digest_sha256 text NOT NULL, + document_record_id uuid NOT NULL, + receipt_digest_sha256 text NOT NULL, + recorded_at timestamptz NOT NULL DEFAULT pg_catalog.transaction_timestamp(), + + CONSTRAINT document_record_persist_idempotency_key_check + CHECK ( + idempotency_key ~ + '^document-record-persist-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' + ), + CONSTRAINT document_record_persist_semantic_digest_check + CHECK (semantic_command_digest_sha256 ~ '^[0-9a-f]{64}$'), + CONSTRAINT document_record_persist_receipt_digest_check + CHECK (receipt_digest_sha256 ~ '^[0-9a-f]{64}$'), + CONSTRAINT document_record_persist_receipt_command_unique + UNIQUE (tenant_record_id, idempotency_key), + CONSTRAINT document_record_persist_receipt_document_unique + UNIQUE (tenant_record_id, document_record_id), + CONSTRAINT document_record_persist_receipt_document_fk + FOREIGN KEY (tenant_record_id, document_record_id) + REFERENCES document_record(tenant_record_id, document_record_id) +); + +COMMENT ON TABLE document_record_persist_receipt IS + 'Append-only, tenant-scoped replay receipt for document_records persistence. It stores only an opaque purpose-bound key, semantic command digest, committed document identity, and receipt digest; document bytes and free-form HR content are excluded.'; + +CREATE TRIGGER document_record_persist_receipt_append_only_guard +BEFORE UPDATE OR DELETE ON document_record_persist_receipt +FOR EACH ROW +EXECUTE FUNCTION public.reject_append_only_mutation(); + +CREATE FUNCTION public.reject_document_record_persist_receipt_truncate() +RETURNS trigger +LANGUAGE plpgsql +SET search_path = pg_catalog, public, pg_temp +AS $$ +BEGIN + RAISE EXCEPTION 'document-record persistence receipts are append-only and cannot be truncated' + USING ERRCODE = '55000'; +END; +$$; + +CREATE TRIGGER document_record_persist_receipt_truncate_guard +BEFORE TRUNCATE ON document_record_persist_receipt +FOR EACH STATEMENT +EXECUTE FUNCTION public.reject_document_record_persist_receipt_truncate(); + +REVOKE TRUNCATE ON document_record_persist_receipt FROM PUBLIC; + +ALTER TABLE document_record_persist_receipt ENABLE ROW LEVEL SECURITY; +ALTER TABLE document_record_persist_receipt FORCE ROW LEVEL SECURITY; + +CREATE POLICY document_record_persist_receipt_tenant_policy +ON document_record_persist_receipt +USING (tenant_record_id = public.current_tenant_record_id()) +WITH CHECK (tenant_record_id = public.current_tenant_record_id()); + +CREATE FUNCTION public.persist_document_record_once( + p_tenant_record_id uuid, + p_idempotency_key text, + p_document_record_id uuid, + p_document_record_reference text, + p_person_record_reference text, + p_employment_record_reference text, + p_uploader_actor_reference text, + p_persisted_by_actor_reference text, + p_document_category_code text, + p_artifact_reference text, + p_artifact_digest_sha256 text, + p_source_provenance_digest_sha256 text, + p_retention_policy_reference text, + p_retention_policy_digest_sha256 text, + p_received_at timestamptz, + p_canonical_evidence_json text, + p_evidence_digest_sha256 text, + p_audit_event_reference text, + p_outbox_event_reference text, + p_application_evidence_digest_sha256 text +) +RETURNS TABLE ( + document_record_id uuid, + document_record_reference text, + audit_event_reference text, + outbox_event_reference text, + semantic_command_digest_sha256 text, + receipt_digest_sha256 text, + recorded_at timestamptz +) +LANGUAGE plpgsql +VOLATILE +SET search_path = pg_catalog, public, pg_temp +AS $$ +DECLARE + v_semantic_command_digest text; + v_existing_semantic_digest text; + v_document_recorded_at timestamptz; + v_receipt_digest text; +BEGIN + IF p_tenant_record_id IS NULL + OR p_idempotency_key IS NULL + OR p_document_record_id IS NULL + OR p_document_record_reference IS NULL + OR p_person_record_reference IS NULL + OR p_employment_record_reference IS NULL + OR p_uploader_actor_reference IS NULL + OR p_persisted_by_actor_reference IS NULL + OR p_document_category_code IS NULL + OR p_artifact_reference IS NULL + OR p_artifact_digest_sha256 IS NULL + OR p_source_provenance_digest_sha256 IS NULL + OR p_retention_policy_reference IS NULL + OR p_retention_policy_digest_sha256 IS NULL + OR p_received_at IS NULL + OR p_canonical_evidence_json IS NULL + OR p_evidence_digest_sha256 IS NULL + OR p_audit_event_reference IS NULL + OR p_outbox_event_reference IS NULL + OR p_application_evidence_digest_sha256 IS NULL THEN + RAISE EXCEPTION 'document persistence command cannot contain null authoritative fields' + USING ERRCODE = '22004'; + END IF; + + IF p_idempotency_key !~ + '^document-record-persist-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' THEN + RAISE EXCEPTION 'document persistence idempotency key must be one opaque purpose-bound UUID reference' + USING ERRCODE = '22023'; + END IF; + + v_semantic_command_digest := pg_catalog.encode( + public.digest( + pg_catalog.convert_to( + pg_catalog.jsonb_build_object( + 'schema_version', 'orgmetra.document_record_persist_command.v1', + 'tenant_record_id', p_tenant_record_id::text, + 'document_record_id', p_document_record_id::text, + 'document_record_reference', p_document_record_reference, + 'person_record_reference', p_person_record_reference, + 'employment_record_reference', p_employment_record_reference, + 'uploader_actor_reference', p_uploader_actor_reference, + 'persisted_by_actor_reference', p_persisted_by_actor_reference, + 'document_category_code', p_document_category_code, + 'artifact_reference', p_artifact_reference, + 'artifact_digest_sha256', p_artifact_digest_sha256, + 'source_provenance_digest_sha256', p_source_provenance_digest_sha256, + 'retention_policy_reference', p_retention_policy_reference, + 'retention_policy_digest_sha256', p_retention_policy_digest_sha256, + 'received_at', p_received_at, + 'canonical_evidence_json', p_canonical_evidence_json, + 'evidence_digest_sha256', p_evidence_digest_sha256, + 'audit_event_reference', p_audit_event_reference, + 'outbox_event_reference', p_outbox_event_reference, + 'application_evidence_digest_sha256', p_application_evidence_digest_sha256, + 'application_purpose_code', 'document_record_persist', + 'application_reason_code', 'reviewed_document_metadata' + )::text, + 'UTF8' + ), + 'sha256' + ), + 'hex' + ); + + -- Match the established People mutation pattern: hold a transaction-scoped + -- key lock only around replay lookup plus the authoritative database write. + PERFORM pg_catalog.pg_advisory_xact_lock( + pg_catalog.hashtextextended( + p_tenant_record_id::text || E'\\x1fdocument_records\\x1f' || p_idempotency_key, + 0 + ) + ); + + SELECT receipt.semantic_command_digest_sha256 + INTO v_existing_semantic_digest + FROM public.document_record_persist_receipt AS receipt + WHERE receipt.tenant_record_id = p_tenant_record_id + AND receipt.idempotency_key = p_idempotency_key; + + IF FOUND THEN + IF v_existing_semantic_digest IS DISTINCT FROM v_semantic_command_digest THEN + RAISE EXCEPTION 'idempotency key is bound to a different document persistence command' + USING ERRCODE = '23514'; + END IF; + + RETURN QUERY + SELECT + persisted.document_record_id, + persisted.document_record_reference, + persisted.audit_event_reference, + persisted.outbox_event_reference, + receipt.semantic_command_digest_sha256, + receipt.receipt_digest_sha256, + persisted.recorded_at + FROM public.document_record_persist_receipt AS receipt + JOIN public.document_record AS persisted + ON persisted.tenant_record_id = receipt.tenant_record_id + AND persisted.document_record_id = receipt.document_record_id + WHERE receipt.tenant_record_id = p_tenant_record_id + AND receipt.idempotency_key = p_idempotency_key; + RETURN; + END IF; + + INSERT INTO public.document_record ( + tenant_record_id, + document_record_id, + document_record_reference, + person_record_reference, + employment_record_reference, + uploader_actor_reference, + persisted_by_actor_reference, + document_category_code, + artifact_reference, + artifact_digest_sha256, + source_provenance_digest_sha256, + retention_policy_reference, + retention_policy_digest_sha256, + received_at, + canonical_evidence_json, + evidence_digest_sha256, + audit_event_reference, + outbox_event_reference, + application_evidence_digest_sha256, + application_purpose_code, + application_reason_code + ) VALUES ( + p_tenant_record_id, + p_document_record_id, + p_document_record_reference, + p_person_record_reference, + p_employment_record_reference, + p_uploader_actor_reference, + p_persisted_by_actor_reference, + p_document_category_code, + p_artifact_reference, + p_artifact_digest_sha256, + p_source_provenance_digest_sha256, + p_retention_policy_reference, + p_retention_policy_digest_sha256, + p_received_at, + p_canonical_evidence_json, + p_evidence_digest_sha256, + p_audit_event_reference, + p_outbox_event_reference, + p_application_evidence_digest_sha256, + 'document_record_persist', + 'reviewed_document_metadata' + ) + RETURNING document_record.recorded_at INTO v_document_recorded_at; + + v_receipt_digest := pg_catalog.encode( + public.digest( + pg_catalog.convert_to( + pg_catalog.jsonb_build_object( + 'schema_version', 'orgmetra.document_record_persist_receipt.v1', + 'tenant_record_id', p_tenant_record_id::text, + 'idempotency_key', p_idempotency_key, + 'semantic_command_digest_sha256', v_semantic_command_digest, + 'document_record_id', p_document_record_id::text, + 'document_record_reference', p_document_record_reference, + 'audit_event_reference', p_audit_event_reference, + 'outbox_event_reference', p_outbox_event_reference, + 'document_recorded_at', v_document_recorded_at + )::text, + 'UTF8' + ), + 'sha256' + ), + 'hex' + ); + + INSERT INTO public.document_record_persist_receipt ( + tenant_record_id, + idempotency_key, + semantic_command_digest_sha256, + document_record_id, + receipt_digest_sha256, + recorded_at + ) VALUES ( + p_tenant_record_id, + p_idempotency_key, + v_semantic_command_digest, + p_document_record_id, + v_receipt_digest, + v_document_recorded_at + ); + + RETURN QUERY + SELECT + p_document_record_id, + p_document_record_reference, + p_audit_event_reference, + p_outbox_event_reference, + v_semantic_command_digest, + v_receipt_digest, + v_document_recorded_at; +END; +$$; + +COMMENT ON FUNCTION public.persist_document_record_once( + uuid, text, uuid, text, text, text, text, text, text, text, text, text, + text, text, timestamptz, text, text, text, text, text +) IS + 'Persists one immutable document-record fact and replay receipt under a tenant-scoped transaction advisory lock. Same-key same-semantic retries return the first committed result; changed semantics fail closed before any second document write.'; + +COMMIT; From 3ef61434b04c6cc01d15788a62e71fc8036ad926 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 09:37:54 +0900 Subject: [PATCH 03/68] docs(document-records): record idempotent persistence decision --- ...-document-record-idempotent-persistence.md | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 docs/adr/0309-document-record-idempotent-persistence.md diff --git a/docs/adr/0309-document-record-idempotent-persistence.md b/docs/adr/0309-document-record-idempotent-persistence.md new file mode 100644 index 000000000..7107bd01a --- /dev/null +++ b/docs/adr/0309-document-record-idempotent-persistence.md @@ -0,0 +1,70 @@ +# ADR 0309: Idempotent document-record persistence under uncertain outcomes + +## Status + +Proposed. This ADR describes the stacked implementation for issue #309 and is not protected-`develop` truth until its prerequisites and this change integrate normally. + +## Problem + +ADR 0107 and migrations 0021–0023 make one `document_record` immutable, tenant-scoped, evidence-bound, unique-key JSON safe, and byte-canonical. Those invariants prevent duplicate durable identities from being silently accepted, but a uniqueness error is not an idempotent result contract. + +A caller can lose the response after PostgreSQL commits. If it retries the same logical persistence command, the owner must distinguish that retry from a different command that reused the same key. Treating a generic unique violation as success would conflate those cases; generating new audit/outbox references on every retry would also make one logical command appear as multiple durable events. + +The lock boundary must remain short. Document parsing, OCR, model inference, artifact transfer, or any other network/compute work is not permitted inside the database transaction used for retry arbitration. + +## Decision + +`document_records` owns a tenant-scoped `document_record_persist_receipt` and the `persist_document_record_once(...)` transaction boundary. + +The command accepts one opaque, purpose-bound idempotency key of the form `document-record-persist-`. It computes a server-side SHA-256 semantic digest over the complete governed persistence command, excluding only PostgreSQL-owned result time. The key itself is not part of the semantic digest; it identifies a retry family rather than changing document semantics. + +Before reading replay state or inserting a document, the function acquires `pg_advisory_xact_lock(hashtextextended(...))` over tenant + `document_records` namespace + key. The lock exists only until the current transaction ends. A same-key concurrent caller therefore waits until the first transaction commits or rolls back. No external I/O occurs while this lock is held. + +After the lock: + +- no receipt means the function inserts exactly one `document_record`, derives a receipt digest from the committed identity/result, and inserts one append-only receipt in the same transaction; +- an existing receipt with the same semantic digest returns the original document identity, document/audit/outbox references, semantic digest, receipt digest, and original database-owned `recorded_at`; +- an existing receipt with a different semantic digest fails closed before any second document write. + +The receipt carries no document bytes, credentials, names, free-form HR content, compensation, ratings, or other duplicated Person/Employment truth. It stores only tenant identity, the opaque idempotency key, semantic digest, committed document identity, receipt digest, and system time. It is FORCE-RLS protected and append-only, including TRUNCATE protection. + +The implementation adds a tenant-qualified unique key to `document_record` so the receipt can use a composite `(tenant_record_id, document_record_id)` foreign key. This preserves the bounded-context invariant that a receipt cannot point at a document from another tenant even if an otherwise valid UUID is supplied. + +## Alternatives considered + +**Return success on a unique violation.** Rejected. A uniqueness violation does not prove that the existing row came from the same semantic command. + +**Retry heuristics in `talent_acquisition` or another consumer.** Rejected. Persistence replay truth belongs to `document_records`; copying mutable owner logic would create two authorities. + +**Hold an explicit transaction open around upstream document processing.** Rejected. That would create the long-lived idle/lock behavior this architecture forbids. All expensive work must finish before entering `persist_document_record_once(...)`. + +**Rely only on `INSERT ... ON CONFLICT`.** Rejected for this increment because the owner must compare a complete semantic digest and return the original result as one contract, not merely suppress a duplicate insert. The transaction-scoped advisory lock follows the already-protected People mutation pattern and makes the replay branch explicit. + +## Evidence and acceptance + +`tests/test_document_record_idempotency_postgres.sh` exercises real PostgreSQL sessions. It proves same-key/same-semantic retry convergence, same-key/different-semantic rejection, concurrent same-semantic convergence while the first transaction remains open, one durable document + one receipt, connection cleanup, receipt FORCE RLS, and append-only mutation rejection. + +PostgreSQL documents `pg_advisory_xact_lock` as an exclusive transaction-level advisory lock that waits when necessary and is automatically released at transaction end. That lifecycle is the reason the lock is acceptable here: its scope contains only replay lookup and the local authoritative write. + +The expired IETF HTTPAPI `Idempotency-Key` Internet-Draft is non-normative background only. Its key principles—one client-generated key for retries and no key reuse with a different payload—are compatible with this design, but the draft expired on 2026-04-18 and is not cited as an active standard. + +## Risks + +Advisory-lock hash collisions can serialize unrelated commands, although they cannot merge their receipt state because the durable key remains tenant + exact idempotency key. The consequence is unnecessary waiting, not cross-command success. + +The semantic digest is versioned as `orgmetra.document_record_persist_command.v1`. Any future change to governed command membership requires a new schema version and migration; silently changing digest membership would break deterministic replay interpretation. + +A caller that abandons a connection mid-transaction relies on PostgreSQL rollback/connection cleanup. Acceptance therefore checks that concurrent test sessions terminate; production pooling/TLS/connection-recovery policy remains an operability concern at the future document-record service adapter. + +## Follow-up + +- Admit `tests/test_document_record_idempotency_postgres.sh` through the owner-neutral PostgreSQL Foundation registry once #310/#311 is reconciled with the document-record stack; do not add a feature-local workflow. +- Add the application/service adapter only after the `document_records` service boundary exists; it must map one external retry key to this transaction without reimplementing replay logic. +- Re-run the full PostgreSQL acceptance on the exact protected-base head before changing this ADR from Proposed. +- Keep #308 return/destruction completion receipts separate: persistence idempotency proves creation/retry identity, not later retention or destruction completion. + +## References + +Jena, J., & Dalal, S. (2025, October 15). *The Idempotency-Key HTTP Header Field* (Internet-Draft draft-ietf-httpapi-idempotency-key-header-07, expired April 18, 2026). Internet Engineering Task Force. https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/ + +PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: Advisory lock functions*. https://www.postgresql.org/docs/18/functions-admin.html From c3ee1faaa30451e4d31fd959cf4abb77c3bd6a07 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 09:40:16 +0900 Subject: [PATCH 04/68] docs(document-records): trace retry persistence evidence --- .../document-record-idempotent-persistence.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs/traceability/document-record-idempotent-persistence.md diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md new file mode 100644 index 000000000..80ecbe518 --- /dev/null +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -0,0 +1,32 @@ +# Document-record idempotent persistence traceability + +Status: active stacked evidence for #309/#312. This file is not protected-`develop` truth until the stack integrates normally. + +| Requirement | Owner artifact | Executable evidence | Current state | +| --- | --- | --- | --- | +| One retry family has one tenant-scoped opaque key | `document_record_persist_receipt.idempotency_key` in migration 0024 | invalid/different-key behavior is constrained by the migration; #312 review remains pending | Implemented on Draft head | +| Same key + same semantic command returns the first committed result | `persist_document_record_once(...)` semantic digest + replay branch | `tests/test_document_record_idempotency_postgres.sh` compares first and retry result bytes and proves one `document_record` + one receipt | Implemented; hosted execution pending Foundation admission | +| Same key + changed semantics fails closed | server-side `orgmetra.document_record_persist_command.v1` SHA-256 | PostgreSQL contract changes only `application_evidence_digest_sha256` and requires the explicit semantic-conflict error | Implemented; hosted execution pending | +| Concurrent first attempts serialize | transaction-scoped `pg_advisory_xact_lock` over tenant + owner namespace + key | two real PostgreSQL sessions; first keeps its transaction open after persistence while the second invokes the same command | Implemented; hosted execution pending | +| No long external operation is inside the lock | ADR 0309 + database-only function body | source inspection: function performs digesting, replay lookup, local inserts, and receipt derivation only | Implemented; service adapter not yet present | +| Receipt cannot bind to another tenant's document | tenant-qualified UNIQUE on `document_record`; composite FK from receipt | migration DDL plus FORCE-RLS acceptance | Implemented; hosted execution pending | +| Receipt state is append-only | append-only row trigger + TRUNCATE trigger | PostgreSQL contract requires UPDATE rejection; table is FORCE RLS | Implemented; hosted execution pending | +| Replay state is PII-minimized | receipt stores tenant, opaque key, digests, document identity, database time only | schema inspection; no document bytes, free-form HR values, credentials, compensation, rating, or duplicated Person/Employment columns | Implemented | +| Lost-response retry can recover authoritative identity | receipt persists in the same transaction as the document write | first result is intentionally ignored by the retry assertion; retry must return the same stored receipt/result | Implemented; hosted execution pending | +| Acceptance connections are closed | test sessions set dedicated `PGAPPNAME` values and are waited before inspection | `pg_stat_activity` must contain zero matching sessions after concurrent acceptance | Implemented; hosted execution pending | +| Foundation cannot silently omit the new PostgreSQL contract | #310/#311 owner-neutral discovery | #310 handoff references `tests/test_document_record_idempotency_postgres.sh`; no feature-local workflow is added | Dependency pending stack reconciliation | +| Creation/retry receipt is not destruction-completion evidence | ADR 0309 / #308 boundary | #307 dependency order keeps #309/#312 and #308 as distinct prerequisites | Explicitly separated | + +## Evidence lineage + +- Parent authority: #107 `7ce73aa44f47113b2ecd42d51bb5d38a22c0367d`. +- RED contract: `6260960f2909d34803dad2890f0c0bfd0f7bede7`. +- Owner migration/function: `b341784aaabca61dff2663986d24fd5e61b5a1c9`. +- ADR 0309: `3ef61434b04c6cc01d15788a62e71fc8036ad926`. +- This traceability update follows those artifacts and must be re-keyed to the final #312 exact head before merge. + +## Evidence limits + +No hosted PostgreSQL execution is claimed on the current stacked branch. #312 targets #107, while the canonical PostgreSQL Foundation implementation is separately stacked under #259/#311. Exact-head GREEN requires ordinary-forward reconciliation of those histories and a fresh run that discovers this contract without filename-specific workflow logic. + +CodeRabbit/Devin status is review evidence only. It is not a substitute for the PostgreSQL runtime contract, required protected-branch gates, or a qualifying independent approval. From a2f4490423b97b21b8f94262157f2270cd53226e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 09:41:04 +0900 Subject: [PATCH 05/68] docs(document-records): align retry evidence with PostgreSQL 16 --- .../0309-document-record-idempotent-persistence.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/adr/0309-document-record-idempotent-persistence.md b/docs/adr/0309-document-record-idempotent-persistence.md index 7107bd01a..19a0de276 100644 --- a/docs/adr/0309-document-record-idempotent-persistence.md +++ b/docs/adr/0309-document-record-idempotent-persistence.md @@ -44,7 +44,9 @@ The implementation adds a tenant-qualified unique key to `document_record` so th `tests/test_document_record_idempotency_postgres.sh` exercises real PostgreSQL sessions. It proves same-key/same-semantic retry convergence, same-key/different-semantic rejection, concurrent same-semantic convergence while the first transaction remains open, one durable document + one receipt, connection cleanup, receipt FORCE RLS, and append-only mutation rejection. -PostgreSQL documents `pg_advisory_xact_lock` as an exclusive transaction-level advisory lock that waits when necessary and is automatically released at transaction end. That lifecycle is the reason the lock is acceptable here: its scope contains only replay lookup and the local authoritative write. +PostgreSQL 16 documents `pg_advisory_xact_lock` as an exclusive transaction-level advisory lock that waits when necessary and is automatically released at transaction end. The function is explicitly `VOLATILE`; PostgreSQL's function-volatility contract gives volatile functions a fresh snapshot for each query they execute under the ordinary Read Committed transaction model. That fresh post-lock lookup is what lets a waiting retry observe the first transaction's committed receipt rather than reinterpret a uniqueness error as success. + +The future service adapter must keep this owner operation at PostgreSQL's ordinary Read Committed isolation unless a later migration supplies equivalent replay semantics for stronger isolation levels. Repeatable Read/Serializable establish longer-lived transaction snapshots; they must not be assumed to provide the same post-wait visibility. This is a contract constraint, not a reason to hold transactions open longer. The expired IETF HTTPAPI `Idempotency-Key` Internet-Draft is non-normative background only. Its key principles—one client-generated key for retries and no key reuse with a different payload—are compatible with this design, but the draft expired on 2026-04-18 and is not cited as an active standard. @@ -56,10 +58,12 @@ The semantic digest is versioned as `orgmetra.document_record_persist_command.v1 A caller that abandons a connection mid-transaction relies on PostgreSQL rollback/connection cleanup. Acceptance therefore checks that concurrent test sessions terminate; production pooling/TLS/connection-recovery policy remains an operability concern at the future document-record service adapter. +A service that silently changes the transaction isolation level could invalidate the fresh-post-lock visibility assumption. Adapter acceptance must assert the supported isolation level before claiming retry convergence; stronger isolation requires an explicit successor design rather than accidental behavior. + ## Follow-up - Admit `tests/test_document_record_idempotency_postgres.sh` through the owner-neutral PostgreSQL Foundation registry once #310/#311 is reconciled with the document-record stack; do not add a feature-local workflow. -- Add the application/service adapter only after the `document_records` service boundary exists; it must map one external retry key to this transaction without reimplementing replay logic. +- Add the application/service adapter only after the `document_records` service boundary exists; it must map one external retry key to this transaction without reimplementing replay logic and must assert the supported transaction isolation. - Re-run the full PostgreSQL acceptance on the exact protected-base head before changing this ADR from Proposed. - Keep #308 return/destruction completion receipts separate: persistence idempotency proves creation/retry identity, not later retention or destruction completion. @@ -67,4 +71,6 @@ A caller that abandons a connection mid-transaction relies on PostgreSQL rollbac Jena, J., & Dalal, S. (2025, October 15). *The Idempotency-Key HTTP Header Field* (Internet-Draft draft-ietf-httpapi-idempotency-key-header-07, expired April 18, 2026). Internet Engineering Task Force. https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/ -PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: Advisory lock functions*. https://www.postgresql.org/docs/18/functions-admin.html +PostgreSQL Global Development Group. (2026). *PostgreSQL 16 documentation: Advisory lock functions*. https://www.postgresql.org/docs/16/functions-admin.html + +PostgreSQL Global Development Group. (2026). *PostgreSQL 16 documentation: Function volatility categories*. https://www.postgresql.org/docs/16/xfunc-volatility.html From db8360801ac852f343cebae5fdd592866c091ae7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 09:42:54 +0900 Subject: [PATCH 06/68] test(document-records): require timezone-stable retry digests --- tests/test_document_record_idempotency_postgres.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_document_record_idempotency_postgres.sh b/tests/test_document_record_idempotency_postgres.sh index c333446f3..dec01a942 100644 --- a/tests/test_document_record_idempotency_postgres.sh +++ b/tests/test_document_record_idempotency_postgres.sh @@ -128,10 +128,10 @@ CANONICAL_EVIDENCE="${evidence_parts[0]}" EVIDENCE_DIGEST="${evidence_parts[1]}" SQL_TEXT="$(persist_sql "${IDEMPOTENCY_KEY}" "${DOCUMENT_ID}" "${DOCUMENT_REFERENCE}" "${ARTIFACT_REFERENCE}" "${AUDIT_REFERENCE}" "${OUTBOX_REFERENCE}" "${APPLICATION_DIGEST}" "${CANONICAL_EVIDENCE}" "${EVIDENCE_DIGEST}")" -first_result="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" -c "${SQL_TEXT}")" -retry_result="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" -c "${SQL_TEXT}")" +first_result="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" -c "SET TIME ZONE 'UTC'; ${SQL_TEXT}")" +retry_result="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" -c "SET TIME ZONE 'Asia/Seoul'; ${SQL_TEXT}")" if [[ "${first_result}" != "${retry_result}" ]]; then - echo "same semantic retry did not converge to the original receipt" >&2 + echo "same semantic retry changed across session time zones instead of returning the original receipt" >&2 exit 1 fi From 00ba4ee03df7ca86bfc3ef2383e04de211532296 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 09:43:45 +0900 Subject: [PATCH 07/68] fix(document-records): canonicalize retry digests in UTC --- .../migrations/0024_document_record_idempotent_persistence.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/database/migrations/0024_document_record_idempotent_persistence.sql b/database/migrations/0024_document_record_idempotent_persistence.sql index 76d49c765..b48b00571 100644 --- a/database/migrations/0024_document_record_idempotent_persistence.sql +++ b/database/migrations/0024_document_record_idempotent_persistence.sql @@ -105,6 +105,7 @@ RETURNS TABLE ( LANGUAGE plpgsql VOLATILE SET search_path = pg_catalog, public, pg_temp +SET TimeZone = 'UTC' AS $$ DECLARE v_semantic_command_digest text; @@ -315,6 +316,6 @@ COMMENT ON FUNCTION public.persist_document_record_once( uuid, text, uuid, text, text, text, text, text, text, text, text, text, text, text, timestamptz, text, text, text, text, text ) IS - 'Persists one immutable document-record fact and replay receipt under a tenant-scoped transaction advisory lock. Same-key same-semantic retries return the first committed result; changed semantics fail closed before any second document write.'; + 'Persists one immutable document-record fact and replay receipt under a tenant-scoped transaction advisory lock. Same-key same-semantic retries return the first committed result; changed semantics fail closed before any second document write. Digest serialization executes with function-local UTC TimeZone so equivalent timestamptz values do not change replay identity across caller sessions.'; COMMIT; From 7a5393c279d9ef65f412a01ab891e71e4585c7fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 09:46:46 +0900 Subject: [PATCH 08/68] test(document-records): fail closed on unsupported retry isolation --- ...t_record_idempotency_isolation_postgres.sh | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/test_document_record_idempotency_isolation_postgres.sh diff --git a/tests/test_document_record_idempotency_isolation_postgres.sh b/tests/test_document_record_idempotency_isolation_postgres.sh new file mode 100644 index 000000000..557d9626c --- /dev/null +++ b/tests/test_document_record_idempotency_isolation_postgres.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" + +for migration in \ + database/migrations/0001_foundation_schema.sql \ + database/migrations/0002_sealed_evidence_digest.sql \ + database/migrations/0021_document_record_persistence.sql \ + database/migrations/0022_document_record_evidence_unique_keys.sql \ + database/migrations/0023_document_record_canonical_encoding.sql \ + database/migrations/0024_document_record_idempotent_persistence.sql; do + if [[ ! -f "${migration}" ]]; then + echo "required document-record idempotency migration is missing: ${migration}" >&2 + exit 1 + fi + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f "${migration}" +done + +set +e +repeatable_read_output="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 2>&1 <<'SQL' +BEGIN ISOLATION LEVEL REPEATABLE READ; +SELECT * +FROM public.persist_document_record_once( + NULL::uuid, + NULL::text, + NULL::uuid, + NULL::text, + NULL::text, + NULL::text, + NULL::text, + NULL::text, + NULL::text, + NULL::text, + NULL::text, + NULL::text, + NULL::text, + NULL::text, + NULL::timestamptz, + NULL::text, + NULL::text, + NULL::text, + NULL::text, + NULL::text +); +ROLLBACK; +SQL +)" +repeatable_read_status=$? +set -e + +if [[ ${repeatable_read_status} -eq 0 \ + || "${repeatable_read_output}" != *"document persistence idempotency requires read committed transaction isolation"* ]]; then + echo "document-record replay boundary did not fail closed under REPEATABLE READ: ${repeatable_read_output}" >&2 + exit 1 +fi + +read_committed="$(psql "${DATABASE_URL}" -Atqc "SHOW transaction_isolation;")" +if [[ "${read_committed}" != "read committed" ]]; then + echo "Foundation PostgreSQL acceptance does not exercise the supported retry isolation: ${read_committed}" >&2 + exit 1 +fi From bfc26948096e72524c434d22a7f6944e8446334b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 09:47:19 +0900 Subject: [PATCH 09/68] fix(document-records): fail closed on unsupported retry isolation --- .../0024_document_record_idempotent_persistence.sql | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/database/migrations/0024_document_record_idempotent_persistence.sql b/database/migrations/0024_document_record_idempotent_persistence.sql index b48b00571..96554aca3 100644 --- a/database/migrations/0024_document_record_idempotent_persistence.sql +++ b/database/migrations/0024_document_record_idempotent_persistence.sql @@ -113,6 +113,11 @@ DECLARE v_document_recorded_at timestamptz; v_receipt_digest text; BEGIN + IF pg_catalog.current_setting('transaction_isolation') IS DISTINCT FROM 'read committed' THEN + RAISE EXCEPTION 'document persistence idempotency requires read committed transaction isolation' + USING ERRCODE = '25000'; + END IF; + IF p_tenant_record_id IS NULL OR p_idempotency_key IS NULL OR p_document_record_id IS NULL @@ -316,6 +321,6 @@ COMMENT ON FUNCTION public.persist_document_record_once( uuid, text, uuid, text, text, text, text, text, text, text, text, text, text, text, timestamptz, text, text, text, text, text ) IS - 'Persists one immutable document-record fact and replay receipt under a tenant-scoped transaction advisory lock. Same-key same-semantic retries return the first committed result; changed semantics fail closed before any second document write. Digest serialization executes with function-local UTC TimeZone so equivalent timestamptz values do not change replay identity across caller sessions.'; + 'Persists one immutable document-record fact and replay receipt under a tenant-scoped transaction advisory lock. The owner fails closed outside Read Committed because replay visibility relies on a fresh post-lock statement snapshot. Same-key same-semantic retries return the first committed result; changed semantics fail closed. Digest serialization uses function-local UTC so equivalent timestamptz values do not change replay identity across caller sessions.'; COMMIT; From 20dc8c8374c46d445de4ab19b67d3cbd95f527b9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 09:47:48 +0900 Subject: [PATCH 10/68] docs(document-records): fail closed outside read committed --- .../adr/0309-document-record-idempotent-persistence.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/adr/0309-document-record-idempotent-persistence.md b/docs/adr/0309-document-record-idempotent-persistence.md index 19a0de276..f15d5a0d6 100644 --- a/docs/adr/0309-document-record-idempotent-persistence.md +++ b/docs/adr/0309-document-record-idempotent-persistence.md @@ -42,11 +42,11 @@ The implementation adds a tenant-qualified unique key to `document_record` so th ## Evidence and acceptance -`tests/test_document_record_idempotency_postgres.sh` exercises real PostgreSQL sessions. It proves same-key/same-semantic retry convergence, same-key/different-semantic rejection, concurrent same-semantic convergence while the first transaction remains open, one durable document + one receipt, connection cleanup, receipt FORCE RLS, and append-only mutation rejection. +`tests/test_document_record_idempotency_postgres.sh` exercises real PostgreSQL sessions. It proves same-key/same-semantic retry convergence, same-key/different-semantic rejection, concurrent same-semantic convergence while the first transaction remains open, one durable document + one receipt, connection cleanup, receipt FORCE RLS, and append-only mutation rejection. The same command is also executed first under UTC and then under Asia/Seoul; digest identity must remain unchanged because the owner function canonicalizes its temporal serialization to UTC. PostgreSQL 16 documents `pg_advisory_xact_lock` as an exclusive transaction-level advisory lock that waits when necessary and is automatically released at transaction end. The function is explicitly `VOLATILE`; PostgreSQL's function-volatility contract gives volatile functions a fresh snapshot for each query they execute under the ordinary Read Committed transaction model. That fresh post-lock lookup is what lets a waiting retry observe the first transaction's committed receipt rather than reinterpret a uniqueness error as success. -The future service adapter must keep this owner operation at PostgreSQL's ordinary Read Committed isolation unless a later migration supplies equivalent replay semantics for stronger isolation levels. Repeatable Read/Serializable establish longer-lived transaction snapshots; they must not be assumed to provide the same post-wait visibility. This is a contract constraint, not a reason to hold transactions open longer. +The owner function now checks `transaction_isolation` before validating or mutating command state and fails closed unless it is `read committed`. `tests/test_document_record_idempotency_isolation_postgres.sh` enters a real `REPEATABLE READ` transaction and requires that isolation error before any command-field validation. Stronger isolation levels therefore cannot silently inherit semantics that depend on a fresh post-lock statement snapshot; a future successor must supply an explicit equivalent algorithm before relaxing this guard. The expired IETF HTTPAPI `Idempotency-Key` Internet-Draft is non-normative background only. Its key principles—one client-generated key for retries and no key reuse with a different payload—are compatible with this design, but the draft expired on 2026-04-18 and is not cited as an active standard. @@ -58,12 +58,12 @@ The semantic digest is versioned as `orgmetra.document_record_persist_command.v1 A caller that abandons a connection mid-transaction relies on PostgreSQL rollback/connection cleanup. Acceptance therefore checks that concurrent test sessions terminate; production pooling/TLS/connection-recovery policy remains an operability concern at the future document-record service adapter. -A service that silently changes the transaction isolation level could invalidate the fresh-post-lock visibility assumption. Adapter acceptance must assert the supported isolation level before claiming retry convergence; stronger isolation requires an explicit successor design rather than accidental behavior. +The explicit Read Committed guard intentionally rejects a caller that promotes this one operation to Repeatable Read or Serializable without a successor design. That is a compatibility boundary, not an invitation to weaken isolation elsewhere: the future adapter must scope transaction policy to this documented write contract. ## Follow-up -- Admit `tests/test_document_record_idempotency_postgres.sh` through the owner-neutral PostgreSQL Foundation registry once #310/#311 is reconciled with the document-record stack; do not add a feature-local workflow. -- Add the application/service adapter only after the `document_records` service boundary exists; it must map one external retry key to this transaction without reimplementing replay logic and must assert the supported transaction isolation. +- Admit both `tests/test_document_record_idempotency_postgres.sh` and `tests/test_document_record_idempotency_isolation_postgres.sh` through the owner-neutral PostgreSQL Foundation registry once #310/#311 is reconciled with the document-record stack; do not add a feature-local workflow. +- Add the application/service adapter only after the `document_records` service boundary exists; it must map one external retry key to this transaction without reimplementing replay logic. - Re-run the full PostgreSQL acceptance on the exact protected-base head before changing this ADR from Proposed. - Keep #308 return/destruction completion receipts separate: persistence idempotency proves creation/retry identity, not later retention or destruction completion. From 45a0296f9ad513b3f73dfce24a09d26d629b447a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 09:48:08 +0900 Subject: [PATCH 11/68] docs(document-records): trace timezone and isolation retry guards --- .../document-record-idempotent-persistence.md | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md index 80ecbe518..a03081126 100644 --- a/docs/traceability/document-record-idempotent-persistence.md +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -5,28 +5,37 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | Requirement | Owner artifact | Executable evidence | Current state | | --- | --- | --- | --- | | One retry family has one tenant-scoped opaque key | `document_record_persist_receipt.idempotency_key` in migration 0024 | invalid/different-key behavior is constrained by the migration; #312 review remains pending | Implemented on Draft head | -| Same key + same semantic command returns the first committed result | `persist_document_record_once(...)` semantic digest + replay branch | `tests/test_document_record_idempotency_postgres.sh` compares first and retry result bytes and proves one `document_record` + one receipt | Implemented; hosted execution pending Foundation admission | +| Same key + same semantic command returns the first committed result | `persist_document_record_once(...)` semantic digest + replay branch | `tests/test_document_record_idempotency_postgres.sh` compares first and retry result and proves one `document_record` + one receipt | Implemented; hosted execution pending Foundation admission | | Same key + changed semantics fails closed | server-side `orgmetra.document_record_persist_command.v1` SHA-256 | PostgreSQL contract changes only `application_evidence_digest_sha256` and requires the explicit semantic-conflict error | Implemented; hosted execution pending | +| Retry identity is independent of caller session timezone | function-local `SET TimeZone = 'UTC'` for digest construction | same semantic command executes first under UTC, then under Asia/Seoul; result/digest identity must remain identical | Implemented; hosted execution pending | +| Unsupported transaction isolation fails closed | `current_setting('transaction_isolation')` guard before command validation/write | `tests/test_document_record_idempotency_isolation_postgres.sh` invokes the owner inside a real `REPEATABLE READ` transaction and requires the explicit isolation error first | Implemented; hosted execution pending | | Concurrent first attempts serialize | transaction-scoped `pg_advisory_xact_lock` over tenant + owner namespace + key | two real PostgreSQL sessions; first keeps its transaction open after persistence while the second invokes the same command | Implemented; hosted execution pending | +| Replay visibility is explicit | `VOLATILE` function + Read Committed guard | ADR 0309 ties post-lock receipt visibility to PostgreSQL statement snapshots and rejects stronger isolation until a successor algorithm exists | Implemented contract | | No long external operation is inside the lock | ADR 0309 + database-only function body | source inspection: function performs digesting, replay lookup, local inserts, and receipt derivation only | Implemented; service adapter not yet present | | Receipt cannot bind to another tenant's document | tenant-qualified UNIQUE on `document_record`; composite FK from receipt | migration DDL plus FORCE-RLS acceptance | Implemented; hosted execution pending | | Receipt state is append-only | append-only row trigger + TRUNCATE trigger | PostgreSQL contract requires UPDATE rejection; table is FORCE RLS | Implemented; hosted execution pending | | Replay state is PII-minimized | receipt stores tenant, opaque key, digests, document identity, database time only | schema inspection; no document bytes, free-form HR values, credentials, compensation, rating, or duplicated Person/Employment columns | Implemented | -| Lost-response retry can recover authoritative identity | receipt persists in the same transaction as the document write | first result is intentionally ignored by the retry assertion; retry must return the same stored receipt/result | Implemented; hosted execution pending | +| Lost-response retry can recover authoritative identity | receipt persists in the same transaction as the document write | first committed result is followed by a separate retry that must return the same stored receipt/result | Implemented; hosted execution pending | | Acceptance connections are closed | test sessions set dedicated `PGAPPNAME` values and are waited before inspection | `pg_stat_activity` must contain zero matching sessions after concurrent acceptance | Implemented; hosted execution pending | -| Foundation cannot silently omit the new PostgreSQL contract | #310/#311 owner-neutral discovery | #310 handoff references `tests/test_document_record_idempotency_postgres.sh`; no feature-local workflow is added | Dependency pending stack reconciliation | +| Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310 handoff references both document-record idempotency PostgreSQL contracts; no feature-local workflow is added | Dependency pending stack reconciliation | | Creation/retry receipt is not destruction-completion evidence | ADR 0309 / #308 boundary | #307 dependency order keeps #309/#312 and #308 as distinct prerequisites | Explicitly separated | ## Evidence lineage - Parent authority: #107 `7ce73aa44f47113b2ecd42d51bb5d38a22c0367d`. -- RED contract: `6260960f2909d34803dad2890f0c0bfd0f7bede7`. -- Owner migration/function: `b341784aaabca61dff2663986d24fd5e61b5a1c9`. -- ADR 0309: `3ef61434b04c6cc01d15788a62e71fc8036ad926`. -- This traceability update follows those artifacts and must be re-keyed to the final #312 exact head before merge. +- Initial RED contract: `6260960f2909d34803dad2890f0c0bfd0f7bede7`. +- Initial owner migration/function: `b341784aaabca61dff2663986d24fd5e61b5a1c9`. +- Initial ADR: `3ef61434b04c6cc01d15788a62e71fc8036ad926`. +- First traceability commit: `c3ee1faaa30451e4d31fd959cf4abb77c3bd6a07`. +- PostgreSQL 16 / Read Committed ADR correction: `a2f4490423b97b21b8f94262157f2270cd53226e`. +- Timezone-drift RED: `db8360801ac852f343cebae5fdd592866c091ae7`. +- Function-local UTC causal fix: `00ba4ee03df7ca86bfc3ef2383e04de211532296`. +- Unsupported-isolation RED: `7a5393c279d9ef65f412a01ab891e71e4585c7fd`. +- Read Committed fail-closed causal fix: `bfc26948096e72524c434d22a7f6944e8446334b`. +- ADR currentization for owner-side isolation enforcement: `20dc8c8374c46d445de4ab19b67d3cbd95f527b9`. ## Evidence limits -No hosted PostgreSQL execution is claimed on the current stacked branch. #312 targets #107, while the canonical PostgreSQL Foundation implementation is separately stacked under #259/#311. Exact-head GREEN requires ordinary-forward reconciliation of those histories and a fresh run that discovers this contract without filename-specific workflow logic. +No hosted PostgreSQL execution is claimed on the current stacked branch. #312 targets #107, while the canonical PostgreSQL Foundation implementation is separately stacked under #259/#311. Exact-head GREEN requires ordinary-forward reconciliation of those histories and a fresh run that discovers both contracts without filename-specific workflow logic. -CodeRabbit/Devin status is review evidence only. It is not a substitute for the PostgreSQL runtime contract, required protected-branch gates, or a qualifying independent approval. +CodeRabbit/Devin status is review evidence only. It is not a substitute for the PostgreSQL runtime contracts, required protected-branch gates, or a qualifying independent approval. From cb7076c49fef13262fcb5c7f300cf902ce6715c9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 10:13:45 +0900 Subject: [PATCH 12/68] test(document-records): reject cross-tenant persistence context --- ...ord_idempotency_tenant_context_postgres.sh | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 tests/test_document_record_idempotency_tenant_context_postgres.sh diff --git a/tests/test_document_record_idempotency_tenant_context_postgres.sh b/tests/test_document_record_idempotency_tenant_context_postgres.sh new file mode 100644 index 000000000..979c3853b --- /dev/null +++ b/tests/test_document_record_idempotency_tenant_context_postgres.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" + +for migration in \ + database/migrations/0001_foundation_schema.sql \ + database/migrations/0002_sealed_evidence_digest.sql \ + database/migrations/0021_document_record_persistence.sql \ + database/migrations/0022_document_record_evidence_unique_keys.sql \ + database/migrations/0023_document_record_canonical_encoding.sql \ + database/migrations/0024_document_record_idempotent_persistence.sql; do + if [[ ! -f "${migration}" ]]; then + echo "required document-record tenant-context migration is missing: ${migration}" >&2 + exit 1 + fi + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f "${migration}" +done + +SESSION_TENANT_ID="10000000-0000-7000-8000-000000000001" +REQUESTED_TENANT_ID="20000000-0000-7000-8000-000000000002" +DOCUMENT_ID="00000000-0000-7000-8000-000000000231" +DOCUMENT_REFERENCE="document_record:00000000-0000-4000-8000-000000000231" +PERSON_REFERENCE="person_record:00000000-0000-4000-8000-000000000211" +EMPLOYMENT_REFERENCE="employment_record:00000000-0000-4000-8000-000000000221" +UPLOADER="actor:00000000-0000-4000-8000-000000000261" +PERSISTED_BY="actor:00000000-0000-4000-8000-000000000262" +ARTIFACT_REFERENCE="document_artifact:00000000-0000-4000-8000-000000000241" +RETENTION_REFERENCE="retention_policy:00000000-0000-4000-8000-000000000251" +AUDIT_REFERENCE="audit_event:00000000-0000-4000-8000-000000000271" +OUTBOX_REFERENCE="outbox_event:00000000-0000-4000-8000-000000000272" +IDEMPOTENCY_KEY="document-record-persist-00000000-0000-4000-8000-000000000201" +ARTIFACT_DIGEST="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +SOURCE_DIGEST="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +RETENTION_DIGEST="cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" +APPLICATION_DIGEST="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + +IFS='|' read -r RECEIVED_AT EVIDENCE_RECORDED_AT < <(psql "${DATABASE_URL}" -Atqc " +SELECT + to_char((pg_catalog.transaction_timestamp() - interval '2 minutes') AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"'), + to_char((pg_catalog.transaction_timestamp() - interval '1 minute') AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"'); +") +export RECEIVED_AT EVIDENCE_RECORDED_AT + +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <&1 <&2 + exit 1 +fi + +cross_tenant_rows="$(psql "${DATABASE_URL}" -Atqc " +SELECT + (SELECT count(*) FROM document_record + WHERE tenant_record_id = '${REQUESTED_TENANT_ID}'::uuid + AND document_record_id = '${DOCUMENT_ID}'::uuid)::text + || '|' || + (SELECT count(*) FROM document_record_persist_receipt + WHERE tenant_record_id = '${REQUESTED_TENANT_ID}'::uuid + AND idempotency_key = '${IDEMPOTENCY_KEY}')::text; +")" +if [[ "${cross_tenant_rows}" != "0|0" ]]; then + echo "cross-tenant persistence left durable document or receipt state: ${cross_tenant_rows}" >&2 + exit 1 +fi + +echo "document-record idempotency tenant-context contract passed" From 147973ef2709dcaffefffa9f40c00f1a49d464d1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 10:14:14 +0900 Subject: [PATCH 13/68] fix(document-records): bind idempotency to tenant context --- .../0024_document_record_idempotent_persistence.sql | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/database/migrations/0024_document_record_idempotent_persistence.sql b/database/migrations/0024_document_record_idempotent_persistence.sql index 96554aca3..154d2c9dc 100644 --- a/database/migrations/0024_document_record_idempotent_persistence.sql +++ b/database/migrations/0024_document_record_idempotent_persistence.sql @@ -142,6 +142,11 @@ BEGIN USING ERRCODE = '22004'; END IF; + IF public.current_tenant_record_id() IS DISTINCT FROM p_tenant_record_id THEN + RAISE EXCEPTION 'document persistence tenant context does not match requested tenant' + USING ERRCODE = '42501'; + END IF; + IF p_idempotency_key !~ '^document-record-persist-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' THEN RAISE EXCEPTION 'document persistence idempotency key must be one opaque purpose-bound UUID reference' @@ -321,6 +326,6 @@ COMMENT ON FUNCTION public.persist_document_record_once( uuid, text, uuid, text, text, text, text, text, text, text, text, text, text, text, timestamptz, text, text, text, text, text ) IS - 'Persists one immutable document-record fact and replay receipt under a tenant-scoped transaction advisory lock. The owner fails closed outside Read Committed because replay visibility relies on a fresh post-lock statement snapshot. Same-key same-semantic retries return the first committed result; changed semantics fail closed. Digest serialization uses function-local UTC so equivalent timestamptz values do not change replay identity across caller sessions.'; + 'Persists one immutable document-record fact and replay receipt under a tenant-scoped transaction advisory lock. The caller tenant context must match the requested tenant before any replay lock or durable write. The owner fails closed outside Read Committed because replay visibility relies on a fresh post-lock statement snapshot. Same-key same-semantic retries return the first committed result; changed semantics fail closed. Digest serialization uses function-local UTC so equivalent timestamptz values do not change replay identity across caller sessions.'; COMMIT; From 4ba85c535626f81468c74f4a5584385b1ad1a883 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 10:14:48 +0900 Subject: [PATCH 14/68] test(document-records): run retries in tenant context --- ...st_document_record_idempotency_postgres.sh | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/test_document_record_idempotency_postgres.sh b/tests/test_document_record_idempotency_postgres.sh index dec01a942..dbb320956 100644 --- a/tests/test_document_record_idempotency_postgres.sh +++ b/tests/test_document_record_idempotency_postgres.sh @@ -43,6 +43,12 @@ INSERT INTO tenant_record (tenant_record_id, tenant_reference) VALUES ('${TENANT_ID}', 'tenant_alpha'), ('${OTHER_TENANT_ID}', 'tenant_beta'); SQL +with_tenant() { + local tenant="$1" + shift + PGOPTIONS="-c orgmetra.tenant_record_id=${tenant} ${PGOPTIONS:-}" command psql "$@" +} + build_evidence() { local document_reference="$1" local artifact_reference="$2" @@ -128,8 +134,8 @@ CANONICAL_EVIDENCE="${evidence_parts[0]}" EVIDENCE_DIGEST="${evidence_parts[1]}" SQL_TEXT="$(persist_sql "${IDEMPOTENCY_KEY}" "${DOCUMENT_ID}" "${DOCUMENT_REFERENCE}" "${ARTIFACT_REFERENCE}" "${AUDIT_REFERENCE}" "${OUTBOX_REFERENCE}" "${APPLICATION_DIGEST}" "${CANONICAL_EVIDENCE}" "${EVIDENCE_DIGEST}")" -first_result="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" -c "SET TIME ZONE 'UTC'; ${SQL_TEXT}")" -retry_result="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" -c "SET TIME ZONE 'Asia/Seoul'; ${SQL_TEXT}")" +first_result="$(with_tenant "${TENANT_ID}" "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" -c "SET TIME ZONE 'UTC'; ${SQL_TEXT}")" +retry_result="$(with_tenant "${TENANT_ID}" "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" -c "SET TIME ZONE 'Asia/Seoul'; ${SQL_TEXT}")" if [[ "${first_result}" != "${retry_result}" ]]; then echo "same semantic retry changed across session time zones instead of returning the original receipt" >&2 exit 1 @@ -147,7 +153,7 @@ if [[ "${counts}" != "1|1" ]]; then fi set +e -conflict_output="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" \ +conflict_output="$(with_tenant "${TENANT_ID}" "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" \ -c "$(persist_sql "${IDEMPOTENCY_KEY}" "${DOCUMENT_ID}" "${DOCUMENT_REFERENCE}" "${ARTIFACT_REFERENCE}" "${AUDIT_REFERENCE}" "${OUTBOX_REFERENCE}" "${CONFLICTING_APPLICATION_DIGEST}" "${CANONICAL_EVIDENCE}" "${EVIDENCE_DIGEST}")" 2>&1)" conflict_status=$? set -e @@ -170,7 +176,9 @@ SECOND_OUTPUT="$(mktemp)" cleanup() { rm -f "${FIRST_OUTPUT}" "${SECOND_OUTPUT}"; } trap cleanup EXIT -PGAPPNAME=orgmetra_document_idempotency_first psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 \ +PGOPTIONS="-c orgmetra.tenant_record_id=${TENANT_ID} ${PGOPTIONS:-}" \ +PGAPPNAME=orgmetra_document_idempotency_first \ +psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 \ -v canonical_evidence="${CONCURRENT_EVIDENCE}" >"${FIRST_OUTPUT}" <"${SECOND_OUTPUT}" & second_pid=$! wait "${first_pid}" From db226ed24c00e692340922afb9150721581fe2ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 10:15:20 +0900 Subject: [PATCH 15/68] docs(document-records): bind retries to tenant context --- ...-document-record-idempotent-persistence.md | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/adr/0309-document-record-idempotent-persistence.md b/docs/adr/0309-document-record-idempotent-persistence.md index f15d5a0d6..badcd122b 100644 --- a/docs/adr/0309-document-record-idempotent-persistence.md +++ b/docs/adr/0309-document-record-idempotent-persistence.md @@ -10,7 +10,7 @@ ADR 0107 and migrations 0021–0023 make one `document_record` immutable, tenant A caller can lose the response after PostgreSQL commits. If it retries the same logical persistence command, the owner must distinguish that retry from a different command that reused the same key. Treating a generic unique violation as success would conflate those cases; generating new audit/outbox references on every retry would also make one logical command appear as multiple durable events. -The lock boundary must remain short. Document parsing, OCR, model inference, artifact transfer, or any other network/compute work is not permitted inside the database transaction used for retry arbitration. +The lock boundary must remain short. Document parsing, OCR, model inference, artifact transfer, or any other network/compute work is not permitted inside the database transaction used for retry arbitration. Tenant identity must also be verified before the function acquires any advisory lock; RLS at the eventual table write is too late because advisory locks are database-global coordination state rather than row-scoped state. ## Decision @@ -18,7 +18,9 @@ The lock boundary must remain short. Document parsing, OCR, model inference, art The command accepts one opaque, purpose-bound idempotency key of the form `document-record-persist-`. It computes a server-side SHA-256 semantic digest over the complete governed persistence command, excluding only PostgreSQL-owned result time. The key itself is not part of the semantic digest; it identifies a retry family rather than changing document semantics. -Before reading replay state or inserting a document, the function acquires `pg_advisory_xact_lock(hashtextextended(...))` over tenant + `document_records` namespace + key. The lock exists only until the current transaction ends. A same-key concurrent caller therefore waits until the first transaction commits or rolls back. No external I/O occurs while this lock is held. +After null-authoritative-field validation, the function requires `current_tenant_record_id()` to equal `p_tenant_record_id`. A mismatch fails with SQLSTATE `42501` before semantic digest computation, replay lookup, advisory-lock acquisition, or any durable write. This explicit owner check remains required even though both document and receipt tables use FORCE RLS: a privileged migration/test connection can bypass RLS, and an advisory lock can otherwise be acquired for another tenant before row security is evaluated. + +Only after that tenant check does the function acquire `pg_advisory_xact_lock(hashtextextended(...))` over tenant + `document_records` namespace + key. The lock exists only until the current transaction ends. A same-key concurrent caller therefore waits until the first transaction commits or rolls back. No external I/O occurs while this lock is held. After the lock: @@ -36,23 +38,27 @@ The implementation adds a tenant-qualified unique key to `document_record` so th **Retry heuristics in `talent_acquisition` or another consumer.** Rejected. Persistence replay truth belongs to `document_records`; copying mutable owner logic would create two authorities. +**Rely on table RLS to reject a mismatched tenant after lock acquisition.** Rejected. Row security protects table access, not database-global advisory-lock ownership. A mismatched request must be rejected before it can coordinate on another tenant's retry key, and privileged maintenance connections must not silently bypass the bounded-context tenant invariant. + **Hold an explicit transaction open around upstream document processing.** Rejected. That would create the long-lived idle/lock behavior this architecture forbids. All expensive work must finish before entering `persist_document_record_once(...)`. **Rely only on `INSERT ... ON CONFLICT`.** Rejected for this increment because the owner must compare a complete semantic digest and return the original result as one contract, not merely suppress a duplicate insert. The transaction-scoped advisory lock follows the already-protected People mutation pattern and makes the replay branch explicit. ## Evidence and acceptance -`tests/test_document_record_idempotency_postgres.sh` exercises real PostgreSQL sessions. It proves same-key/same-semantic retry convergence, same-key/different-semantic rejection, concurrent same-semantic convergence while the first transaction remains open, one durable document + one receipt, connection cleanup, receipt FORCE RLS, and append-only mutation rejection. The same command is also executed first under UTC and then under Asia/Seoul; digest identity must remain unchanged because the owner function canonicalizes its temporal serialization to UTC. +`tests/test_document_record_idempotency_postgres.sh` exercises real PostgreSQL sessions. It proves same-key/same-semantic retry convergence, same-key/different-semantic rejection, concurrent same-semantic convergence while the first transaction remains open, one durable document + one receipt, connection cleanup, receipt FORCE RLS, and append-only mutation rejection. The same command is also executed first under UTC and then under Asia/Seoul; digest identity must remain unchanged because the owner function canonicalizes its temporal serialization to UTC. All supported calls now provide the tenant session context explicitly instead of relying on a privileged test owner. + +`tests/test_document_record_idempotency_tenant_context_postgres.sh` supplies a valid tenant-beta command while the session tenant is tenant-alpha and requires the explicit owner error `document persistence tenant context does not match requested tenant`. It also proves that the rejected attempt leaves zero beta document and receipt rows. This contract intentionally remains valid even when the Foundation database owner can bypass RLS, because the owner function itself must enforce the tenant boundary before advisory-lock acquisition. PostgreSQL 16 documents `pg_advisory_xact_lock` as an exclusive transaction-level advisory lock that waits when necessary and is automatically released at transaction end. The function is explicitly `VOLATILE`; PostgreSQL's function-volatility contract gives volatile functions a fresh snapshot for each query they execute under the ordinary Read Committed transaction model. That fresh post-lock lookup is what lets a waiting retry observe the first transaction's committed receipt rather than reinterpret a uniqueness error as success. -The owner function now checks `transaction_isolation` before validating or mutating command state and fails closed unless it is `read committed`. `tests/test_document_record_idempotency_isolation_postgres.sh` enters a real `REPEATABLE READ` transaction and requires that isolation error before any command-field validation. Stronger isolation levels therefore cannot silently inherit semantics that depend on a fresh post-lock statement snapshot; a future successor must supply an explicit equivalent algorithm before relaxing this guard. +The owner function checks `transaction_isolation` before validating or mutating command state and fails closed unless it is `read committed`. `tests/test_document_record_idempotency_isolation_postgres.sh` enters a real `REPEATABLE READ` transaction and requires that isolation error before any command-field validation. Stronger isolation levels therefore cannot silently inherit semantics that depend on a fresh post-lock statement snapshot; a future successor must supply an explicit equivalent algorithm before relaxing this guard. The expired IETF HTTPAPI `Idempotency-Key` Internet-Draft is non-normative background only. Its key principles—one client-generated key for retries and no key reuse with a different payload—are compatible with this design, but the draft expired on 2026-04-18 and is not cited as an active standard. ## Risks -Advisory-lock hash collisions can serialize unrelated commands, although they cannot merge their receipt state because the durable key remains tenant + exact idempotency key. The consequence is unnecessary waiting, not cross-command success. +Advisory-lock hash collisions can serialize unrelated commands, although they cannot merge their receipt state because the durable key remains tenant + exact idempotency key. The consequence is unnecessary waiting, not cross-command success. The explicit tenant-context guard prevents a caller from intentionally acquiring this coordination state for a different tenant through `persist_document_record_once(...)`. The semantic digest is versioned as `orgmetra.document_record_persist_command.v1`. Any future change to governed command membership requires a new schema version and migration; silently changing digest membership would break deterministic replay interpretation. @@ -62,8 +68,8 @@ The explicit Read Committed guard intentionally rejects a caller that promotes t ## Follow-up -- Admit both `tests/test_document_record_idempotency_postgres.sh` and `tests/test_document_record_idempotency_isolation_postgres.sh` through the owner-neutral PostgreSQL Foundation registry once #310/#311 is reconciled with the document-record stack; do not add a feature-local workflow. -- Add the application/service adapter only after the `document_records` service boundary exists; it must map one external retry key to this transaction without reimplementing replay logic. +- Admit `tests/test_document_record_idempotency_postgres.sh`, `tests/test_document_record_idempotency_isolation_postgres.sh`, and `tests/test_document_record_idempotency_tenant_context_postgres.sh` through the owner-neutral PostgreSQL Foundation registry once #310/#311 is reconciled with the document-record stack; do not add a feature-local workflow. +- Add the application/service adapter only after the `document_records` service boundary exists; it must map one external retry key and authenticated tenant context to this transaction without reimplementing replay logic. - Re-run the full PostgreSQL acceptance on the exact protected-base head before changing this ADR from Proposed. - Keep #308 return/destruction completion receipts separate: persistence idempotency proves creation/retry identity, not later retention or destruction completion. From 8ba15958762ed49bc33c4f6d78573b0427bec927 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 10:15:43 +0900 Subject: [PATCH 16/68] docs(document-records): trace tenant-bound retry evidence --- .../document-record-idempotent-persistence.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md index a03081126..a1a482711 100644 --- a/docs/traceability/document-record-idempotent-persistence.md +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -5,11 +5,12 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | Requirement | Owner artifact | Executable evidence | Current state | | --- | --- | --- | --- | | One retry family has one tenant-scoped opaque key | `document_record_persist_receipt.idempotency_key` in migration 0024 | invalid/different-key behavior is constrained by the migration; #312 review remains pending | Implemented on Draft head | -| Same key + same semantic command returns the first committed result | `persist_document_record_once(...)` semantic digest + replay branch | `tests/test_document_record_idempotency_postgres.sh` compares first and retry result and proves one `document_record` + one receipt | Implemented; hosted execution pending Foundation admission | +| Session tenant must match requested tenant before retry coordination | explicit `current_tenant_record_id() = p_tenant_record_id` guard before semantic digest/replay lock | `tests/test_document_record_idempotency_tenant_context_postgres.sh` submits a valid tenant-beta command from a tenant-alpha session, requires SQLSTATE-42501 boundary text, and proves zero beta document/receipt rows | Implemented; hosted execution pending Foundation admission | +| Same key + same semantic command returns the first committed result | `persist_document_record_once(...)` semantic digest + replay branch | `tests/test_document_record_idempotency_postgres.sh` compares first and retry result and proves one `document_record` + one receipt while all supported calls set the tenant session context | Implemented; hosted execution pending Foundation admission | | Same key + changed semantics fails closed | server-side `orgmetra.document_record_persist_command.v1` SHA-256 | PostgreSQL contract changes only `application_evidence_digest_sha256` and requires the explicit semantic-conflict error | Implemented; hosted execution pending | | Retry identity is independent of caller session timezone | function-local `SET TimeZone = 'UTC'` for digest construction | same semantic command executes first under UTC, then under Asia/Seoul; result/digest identity must remain identical | Implemented; hosted execution pending | | Unsupported transaction isolation fails closed | `current_setting('transaction_isolation')` guard before command validation/write | `tests/test_document_record_idempotency_isolation_postgres.sh` invokes the owner inside a real `REPEATABLE READ` transaction and requires the explicit isolation error first | Implemented; hosted execution pending | -| Concurrent first attempts serialize | transaction-scoped `pg_advisory_xact_lock` over tenant + owner namespace + key | two real PostgreSQL sessions; first keeps its transaction open after persistence while the second invokes the same command | Implemented; hosted execution pending | +| Concurrent first attempts serialize | transaction-scoped `pg_advisory_xact_lock` over tenant + owner namespace + key | two real PostgreSQL sessions with explicit tenant context; first keeps its transaction open after persistence while the second invokes the same command | Implemented; hosted execution pending | | Replay visibility is explicit | `VOLATILE` function + Read Committed guard | ADR 0309 ties post-lock receipt visibility to PostgreSQL statement snapshots and rejects stronger isolation until a successor algorithm exists | Implemented contract | | No long external operation is inside the lock | ADR 0309 + database-only function body | source inspection: function performs digesting, replay lookup, local inserts, and receipt derivation only | Implemented; service adapter not yet present | | Receipt cannot bind to another tenant's document | tenant-qualified UNIQUE on `document_record`; composite FK from receipt | migration DDL plus FORCE-RLS acceptance | Implemented; hosted execution pending | @@ -17,7 +18,7 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | Replay state is PII-minimized | receipt stores tenant, opaque key, digests, document identity, database time only | schema inspection; no document bytes, free-form HR values, credentials, compensation, rating, or duplicated Person/Employment columns | Implemented | | Lost-response retry can recover authoritative identity | receipt persists in the same transaction as the document write | first committed result is followed by a separate retry that must return the same stored receipt/result | Implemented; hosted execution pending | | Acceptance connections are closed | test sessions set dedicated `PGAPPNAME` values and are waited before inspection | `pg_stat_activity` must contain zero matching sessions after concurrent acceptance | Implemented; hosted execution pending | -| Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310 handoff references both document-record idempotency PostgreSQL contracts; no feature-local workflow is added | Dependency pending stack reconciliation | +| Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310/#311 must discover all three document-record idempotency PostgreSQL contracts; no feature-local workflow is added | Dependency pending stack reconciliation | | Creation/retry receipt is not destruction-completion evidence | ADR 0309 / #308 boundary | #307 dependency order keeps #309/#312 and #308 as distinct prerequisites | Explicitly separated | ## Evidence lineage @@ -33,9 +34,13 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel - Unsupported-isolation RED: `7a5393c279d9ef65f412a01ab891e71e4585c7fd`. - Read Committed fail-closed causal fix: `bfc26948096e72524c434d22a7f6944e8446334b`. - ADR currentization for owner-side isolation enforcement: `20dc8c8374c46d445de4ab19b67d3cbd95f527b9`. +- Cross-tenant retry RED contract: `cb7076c49fef13262fcb5c7f300cf902ce6715c9`. +- Pre-lock tenant-context causal fix: `147973ef2709dcaffefffa9f40c00f1a49d464d1`. +- Existing idempotency acceptance repaired to provide tenant context: `4ba85c535626f81468c74f4a5584385b1ad1a883`. +- ADR currentization for tenant-bound retry coordination: `db226ed24c00e692340922afb9150721581fe2ab`. ## Evidence limits -No hosted PostgreSQL execution is claimed on the current stacked branch. #312 targets #107, while the canonical PostgreSQL Foundation implementation is separately stacked under #259/#311. Exact-head GREEN requires ordinary-forward reconciliation of those histories and a fresh run that discovers both contracts without filename-specific workflow logic. +No hosted PostgreSQL execution is claimed on the current stacked branch. #312 targets #107, while the canonical PostgreSQL Foundation implementation is separately stacked under #259/#311. Exact-head GREEN requires ordinary-forward reconciliation of those histories and a fresh run that discovers all three contracts without filename-specific workflow logic. CodeRabbit/Devin status is review evidence only. It is not a substitute for the PostgreSQL runtime contracts, required protected-branch gates, or a qualifying independent approval. From 9080e210b607606940ce0dbd208aa1e44d64422f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 11:05:02 +0900 Subject: [PATCH 17/68] test(document-records): prove advisory serialization and receipt RLS --- ...st_document_record_idempotency_postgres.sh | 141 ++++++++++++++++-- 1 file changed, 128 insertions(+), 13 deletions(-) diff --git a/tests/test_document_record_idempotency_postgres.sh b/tests/test_document_record_idempotency_postgres.sh index dbb320956..5e84ad176 100644 --- a/tests/test_document_record_idempotency_postgres.sh +++ b/tests/test_document_record_idempotency_postgres.sh @@ -171,29 +171,95 @@ mapfile -t concurrent_evidence_parts < <(build_evidence "${CONCURRENT_DOCUMENT_R CONCURRENT_EVIDENCE="${concurrent_evidence_parts[0]}" CONCURRENT_EVIDENCE_DIGEST="${concurrent_evidence_parts[1]}" CONCURRENT_SQL="$(persist_sql "${CONCURRENT_KEY}" "${CONCURRENT_DOCUMENT_ID}" "${CONCURRENT_DOCUMENT_REFERENCE}" "${CONCURRENT_ARTIFACT_REFERENCE}" "${CONCURRENT_AUDIT_REFERENCE}" "${CONCURRENT_OUTBOX_REFERENCE}" "${APPLICATION_DIGEST}" "${CONCURRENT_EVIDENCE}" "${CONCURRENT_EVIDENCE_DIGEST}")" +FIRST_INPUT="$(mktemp -u)" FIRST_OUTPUT="$(mktemp)" SECOND_OUTPUT="$(mktemp)" -cleanup() { rm -f "${FIRST_OUTPUT}" "${SECOND_OUTPUT}"; } +mkfifo "${FIRST_INPUT}" +first_client_pid="" +second_client_pid="" +cleanup() { + exec 3>&- 2>/dev/null || true + if [[ -n "${second_client_pid}" ]] && kill -0 "${second_client_pid}" 2>/dev/null; then + kill "${second_client_pid}" 2>/dev/null || true + fi + if [[ -n "${first_client_pid}" ]] && kill -0 "${first_client_pid}" 2>/dev/null; then + kill "${first_client_pid}" 2>/dev/null || true + fi + rm -f "${FIRST_INPUT}" "${FIRST_OUTPUT}" "${SECOND_OUTPUT}" +} trap cleanup EXIT PGOPTIONS="-c orgmetra.tenant_record_id=${TENANT_ID} ${PGOPTIONS:-}" \ PGAPPNAME=orgmetra_document_idempotency_first \ psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 \ - -v canonical_evidence="${CONCURRENT_EVIDENCE}" >"${FIRST_OUTPUT}" <"${FIRST_OUTPUT}" 2>&1 & +first_client_pid=$! +exec 3>"${FIRST_INPUT}" +printf 'BEGIN;\nSELECT '\''FIRST_BACKEND|''' || pg_backend_pid()::text;\n%s\nSELECT '\''FIRST_LOCK_HELD''';\n' "${CONCURRENT_SQL}" >&3 + +first_ready=false +first_deadline=$((SECONDS + 10)) +while (( SECONDS < first_deadline )); do + if grep -q '^FIRST_LOCK_HELD$' "${FIRST_OUTPUT}"; then + first_ready=true + break + fi + if ! kill -0 "${first_client_pid}" 2>/dev/null; then + break + fi + sleep 0.05 +done +if [[ "${first_ready}" != "true" ]]; then + echo "first concurrent session did not reach the held transaction boundary" >&2 + cat "${FIRST_OUTPUT}" >&2 + exit 1 +fi +FIRST_BACKEND_PID="$(sed -n 's/^FIRST_BACKEND|//p' "${FIRST_OUTPUT}" | head -n 1)" +if [[ ! "${FIRST_BACKEND_PID}" =~ ^[0-9]+$ ]]; then + echo "could not capture first PostgreSQL backend pid: ${FIRST_BACKEND_PID}" >&2 + exit 1 +fi + PGOPTIONS="-c orgmetra.tenant_record_id=${TENANT_ID} ${PGOPTIONS:-}" \ PGAPPNAME=orgmetra_document_idempotency_second \ psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 \ - -v canonical_evidence="${CONCURRENT_EVIDENCE}" -c "${CONCURRENT_SQL}" >"${SECOND_OUTPUT}" & -second_pid=$! -wait "${first_pid}" -wait "${second_pid}" + -v canonical_evidence="${CONCURRENT_EVIDENCE}" -c "${CONCURRENT_SQL}" >"${SECOND_OUTPUT}" 2>&1 & +second_client_pid=$! + +advisory_wait_observed=false +second_deadline=$((SECONDS + 10)) +while (( SECONDS < second_deadline )); do + advisory_wait_count="$(psql "${DATABASE_URL}" -Atqc " +SELECT count(*) +FROM pg_catalog.pg_stat_activity AS activity +JOIN pg_catalog.pg_locks AS lock_state + ON lock_state.pid = activity.pid +WHERE activity.application_name = 'orgmetra_document_idempotency_second' + AND lock_state.locktype = 'advisory' + AND NOT lock_state.granted + AND ${FIRST_BACKEND_PID} = ANY(pg_catalog.pg_blocking_pids(activity.pid)); +")" + if [[ "${advisory_wait_count}" == "1" ]]; then + advisory_wait_observed=true + break + fi + if ! kill -0 "${second_client_pid}" 2>/dev/null; then + break + fi + sleep 0.05 +done +if [[ "${advisory_wait_observed}" != "true" ]]; then + echo "second concurrent session did not demonstrably wait on the first session's advisory lock" >&2 + cat "${SECOND_OUTPUT}" >&2 + exit 1 +fi + +printf 'COMMIT;\n\\q\n' >&3 +exec 3>&- +wait "${first_client_pid}" +first_client_pid="" +wait "${second_client_pid}" +second_client_pid="" first_concurrent_result="$(grep -F 'document_record:' "${FIRST_OUTPUT}" | head -n 1)" second_concurrent_result="$(grep -F 'document_record:' "${SECOND_OUTPUT}" | head -n 1)" if [[ -z "${first_concurrent_result}" || "${first_concurrent_result}" != "${second_concurrent_result}" ]]; then @@ -230,6 +296,55 @@ if [[ "${rls_state}" != "true|true" ]]; then exit 1 fi +PROBE_ROLE="orgmetra_document_receipt_probe" +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <&2 + exit 1 +fi + +other_tenant_receipts="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 <&2 + exit 1 +fi + +cross_tenant_update="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 <&2 + exit 1 +fi + +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 < Date: Sat, 12 Sep 2026 11:05:56 +0900 Subject: [PATCH 18/68] fix(document-records): synchronize idempotency concurrency acceptance --- ...st_document_record_idempotency_postgres.sh | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/test_document_record_idempotency_postgres.sh b/tests/test_document_record_idempotency_postgres.sh index 5e84ad176..b9e4f8f22 100644 --- a/tests/test_document_record_idempotency_postgres.sh +++ b/tests/test_document_record_idempotency_postgres.sh @@ -171,9 +171,10 @@ mapfile -t concurrent_evidence_parts < <(build_evidence "${CONCURRENT_DOCUMENT_R CONCURRENT_EVIDENCE="${concurrent_evidence_parts[0]}" CONCURRENT_EVIDENCE_DIGEST="${concurrent_evidence_parts[1]}" CONCURRENT_SQL="$(persist_sql "${CONCURRENT_KEY}" "${CONCURRENT_DOCUMENT_ID}" "${CONCURRENT_DOCUMENT_REFERENCE}" "${CONCURRENT_ARTIFACT_REFERENCE}" "${CONCURRENT_AUDIT_REFERENCE}" "${CONCURRENT_OUTBOX_REFERENCE}" "${APPLICATION_DIGEST}" "${CONCURRENT_EVIDENCE}" "${CONCURRENT_EVIDENCE_DIGEST}")" -FIRST_INPUT="$(mktemp -u)" -FIRST_OUTPUT="$(mktemp)" -SECOND_OUTPUT="$(mktemp)" +CONCURRENCY_DIR="$(mktemp -d)" +FIRST_INPUT="${CONCURRENCY_DIR}/first-input" +FIRST_OUTPUT="${CONCURRENCY_DIR}/first-output" +SECOND_OUTPUT="${CONCURRENCY_DIR}/second-output" mkfifo "${FIRST_INPUT}" first_client_pid="" second_client_pid="" @@ -185,7 +186,7 @@ cleanup() { if [[ -n "${first_client_pid}" ]] && kill -0 "${first_client_pid}" 2>/dev/null; then kill "${first_client_pid}" 2>/dev/null || true fi - rm -f "${FIRST_INPUT}" "${FIRST_OUTPUT}" "${SECOND_OUTPUT}" + rm -rf "${CONCURRENCY_DIR}" } trap cleanup EXIT @@ -195,13 +196,21 @@ psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 \ -v canonical_evidence="${CONCURRENT_EVIDENCE}" <"${FIRST_INPUT}" >"${FIRST_OUTPUT}" 2>&1 & first_client_pid=$! exec 3>"${FIRST_INPUT}" -printf 'BEGIN;\nSELECT '\''FIRST_BACKEND|''' || pg_backend_pid()::text;\n%s\nSELECT '\''FIRST_LOCK_HELD''';\n' "${CONCURRENT_SQL}" >&3 +cat >&3 </dev/null; then @@ -209,14 +218,9 @@ while (( SECONDS < first_deadline )); do fi sleep 0.05 done -if [[ "${first_ready}" != "true" ]]; then - echo "first concurrent session did not reach the held transaction boundary" >&2 - cat "${FIRST_OUTPUT}" >&2 - exit 1 -fi -FIRST_BACKEND_PID="$(sed -n 's/^FIRST_BACKEND|//p' "${FIRST_OUTPUT}" | head -n 1)" if [[ ! "${FIRST_BACKEND_PID}" =~ ^[0-9]+$ ]]; then - echo "could not capture first PostgreSQL backend pid: ${FIRST_BACKEND_PID}" >&2 + echo "first concurrent session did not reach an observable held transaction boundary" >&2 + cat "${FIRST_OUTPUT}" >&2 exit 1 fi From 8d28ab4a8beaf21a6405fce9b23b3642b7008924 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 11:06:31 +0900 Subject: [PATCH 19/68] docs(document-records): trace executable retry serialization evidence --- .../document-record-idempotent-persistence.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md index a1a482711..56dcff639 100644 --- a/docs/traceability/document-record-idempotent-persistence.md +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -10,11 +10,12 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | Same key + changed semantics fails closed | server-side `orgmetra.document_record_persist_command.v1` SHA-256 | PostgreSQL contract changes only `application_evidence_digest_sha256` and requires the explicit semantic-conflict error | Implemented; hosted execution pending | | Retry identity is independent of caller session timezone | function-local `SET TimeZone = 'UTC'` for digest construction | same semantic command executes first under UTC, then under Asia/Seoul; result/digest identity must remain identical | Implemented; hosted execution pending | | Unsupported transaction isolation fails closed | `current_setting('transaction_isolation')` guard before command validation/write | `tests/test_document_record_idempotency_isolation_postgres.sh` invokes the owner inside a real `REPEATABLE READ` transaction and requires the explicit isolation error first | Implemented; hosted execution pending | -| Concurrent first attempts serialize | transaction-scoped `pg_advisory_xact_lock` over tenant + owner namespace + key | two real PostgreSQL sessions with explicit tenant context; first keeps its transaction open after persistence while the second invokes the same command | Implemented; hosted execution pending | +| Concurrent first attempts demonstrably serialize on the owner advisory lock | transaction-scoped `pg_advisory_xact_lock` over tenant + owner namespace + key | the first real PostgreSQL session remains `idle in transaction` after persistence; the second must expose an ungranted `advisory` lock in `pg_locks` and `pg_blocking_pids(...)` must name the first backend before the test permits the first transaction to commit | Implemented acceptance; hosted execution pending | | Replay visibility is explicit | `VOLATILE` function + Read Committed guard | ADR 0309 ties post-lock receipt visibility to PostgreSQL statement snapshots and rejects stronger isolation until a successor algorithm exists | Implemented contract | | No long external operation is inside the lock | ADR 0309 + database-only function body | source inspection: function performs digesting, replay lookup, local inserts, and receipt derivation only | Implemented; service adapter not yet present | -| Receipt cannot bind to another tenant's document | tenant-qualified UNIQUE on `document_record`; composite FK from receipt | migration DDL plus FORCE-RLS acceptance | Implemented; hosted execution pending | -| Receipt state is append-only | append-only row trigger + TRUNCATE trigger | PostgreSQL contract requires UPDATE rejection; table is FORCE RLS | Implemented; hosted execution pending | +| Receipt cannot bind to another tenant's document | tenant-qualified UNIQUE on `document_record`; composite FK from receipt | migration DDL plus PostgreSQL acceptance | Implemented; hosted execution pending | +| Receipt RLS is behavioral, not metadata-only | FORCE RLS policy on `document_record_persist_receipt` | a temporary `NOBYPASSRLS`/non-superuser role granted only receipt SELECT/UPDATE can read its own tenant's receipts, sees zero rows under another tenant context, and cannot update hidden cross-tenant rows | Implemented acceptance; hosted execution pending | +| Receipt state is append-only | append-only row trigger + TRUNCATE trigger | PostgreSQL contract requires same-tenant UPDATE rejection in addition to cross-tenant RLS invisibility | Implemented; hosted execution pending | | Replay state is PII-minimized | receipt stores tenant, opaque key, digests, document identity, database time only | schema inspection; no document bytes, free-form HR values, credentials, compensation, rating, or duplicated Person/Employment columns | Implemented | | Lost-response retry can recover authoritative identity | receipt persists in the same transaction as the document write | first committed result is followed by a separate retry that must return the same stored receipt/result | Implemented; hosted execution pending | | Acceptance connections are closed | test sessions set dedicated `PGAPPNAME` values and are waited before inspection | `pg_stat_activity` must contain zero matching sessions after concurrent acceptance | Implemented; hosted execution pending | @@ -38,9 +39,12 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel - Pre-lock tenant-context causal fix: `147973ef2709dcaffefffa9f40c00f1a49d464d1`. - Existing idempotency acceptance repaired to provide tenant context: `4ba85c535626f81468c74f4a5584385b1ad1a883`. - ADR currentization for tenant-bound retry coordination: `db226ed24c00e692340922afb9150721581fe2ab`. +- Deterministic concurrency/RLS acceptance repair after review finding: `262122bfe0f959d5225e57bf8de9f9e54018af13`. ## Evidence limits No hosted PostgreSQL execution is claimed on the current stacked branch. #312 targets #107, while the canonical PostgreSQL Foundation implementation is separately stacked under #259/#311. Exact-head GREEN requires ordinary-forward reconciliation of those histories and a fresh run that discovers all three contracts without filename-specific workflow logic. +The bounded observation loops in the concurrency test only wait for PostgreSQL's explicit `pg_stat_activity`/`pg_locks` state. They do not use elapsed time as evidence that serialization happened: the acceptance fails unless the second backend is actually shown waiting on the first backend's advisory lock. + CodeRabbit/Devin status is review evidence only. It is not a substitute for the PostgreSQL runtime contracts, required protected-branch gates, or a qualifying independent approval. From 4757e24ae03db08c10d93ccf07f6ddbf9fe73a85 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 12:02:52 +0900 Subject: [PATCH 20/68] test(document-records): clean RLS probe role on failure --- ...st_document_record_idempotency_postgres.sh | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/test_document_record_idempotency_postgres.sh b/tests/test_document_record_idempotency_postgres.sh index b9e4f8f22..8620c5562 100644 --- a/tests/test_document_record_idempotency_postgres.sh +++ b/tests/test_document_record_idempotency_postgres.sh @@ -178,6 +178,20 @@ SECOND_OUTPUT="${CONCURRENCY_DIR}/second-output" mkfifo "${FIRST_INPUT}" first_client_pid="" second_client_pid="" +PROBE_ROLE="orgmetra_document_receipt_probe_${BASHPID}" + +cleanup_probe_role() { + local role_exists + role_exists="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -c \ + "SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '${PROBE_ROLE}';" 2>/dev/null || true)" + if [[ "${role_exists}" == "1" ]]; then + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 >/dev/null 2>&1 <&- 2>/dev/null || true if [[ -n "${second_client_pid}" ]] && kill -0 "${second_client_pid}" 2>/dev/null; then @@ -186,6 +200,7 @@ cleanup() { if [[ -n "${first_client_pid}" ]] && kill -0 "${first_client_pid}" 2>/dev/null; then kill "${first_client_pid}" 2>/dev/null || true fi + cleanup_probe_role rm -rf "${CONCURRENCY_DIR}" } trap cleanup EXIT @@ -300,7 +315,6 @@ if [[ "${rls_state}" != "true|true" ]]; then exit 1 fi -PROBE_ROLE="orgmetra_document_receipt_probe" psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 < Date: Sat, 12 Sep 2026 12:06:29 +0900 Subject: [PATCH 21/68] test(document-records): fail closed on probe cleanup --- ...st_document_record_idempotency_postgres.sh | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/tests/test_document_record_idempotency_postgres.sh b/tests/test_document_record_idempotency_postgres.sh index 8620c5562..333777c33 100644 --- a/tests/test_document_record_idempotency_postgres.sh +++ b/tests/test_document_record_idempotency_postgres.sh @@ -181,15 +181,32 @@ second_client_pid="" PROBE_ROLE="orgmetra_document_receipt_probe_${BASHPID}" cleanup_probe_role() { + local mode="${1:-strict}" local role_exists - role_exists="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -c \ - "SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '${PROBE_ROLE}';" 2>/dev/null || true)" - if [[ "${role_exists}" == "1" ]]; then - psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 >/dev/null 2>&1 </dev/null)"; then + if [[ "${mode}" == "best-effort" ]]; then + return 0 + fi + echo "could not verify temporary RLS probe-role cleanup" >&2 + return 1 + fi + if [[ "${role_exists}" != "1" ]]; then + return 0 + fi + if psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 >/dev/null 2>&1 <&2 + return 1 } cleanup() { @@ -200,7 +217,7 @@ cleanup() { if [[ -n "${first_client_pid}" ]] && kill -0 "${first_client_pid}" 2>/dev/null; then kill "${first_client_pid}" 2>/dev/null || true fi - cleanup_probe_role + cleanup_probe_role best-effort rm -rf "${CONCURRENCY_DIR}" } trap cleanup EXIT @@ -358,7 +375,7 @@ if [[ -n "${cross_tenant_update}" ]]; then exit 1 fi -cleanup_probe_role +cleanup_probe_role strict set +e mutation_output="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -c " From a9dc83aaf139f4f0bf0ee1ddb70cf2ba37000866 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 12:07:47 +0900 Subject: [PATCH 22/68] docs(document-records): trace probe cleanup evidence --- docs/traceability/document-record-idempotent-persistence.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md index 56dcff639..71c4b5179 100644 --- a/docs/traceability/document-record-idempotent-persistence.md +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -15,6 +15,7 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | No long external operation is inside the lock | ADR 0309 + database-only function body | source inspection: function performs digesting, replay lookup, local inserts, and receipt derivation only | Implemented; service adapter not yet present | | Receipt cannot bind to another tenant's document | tenant-qualified UNIQUE on `document_record`; composite FK from receipt | migration DDL plus PostgreSQL acceptance | Implemented; hosted execution pending | | Receipt RLS is behavioral, not metadata-only | FORCE RLS policy on `document_record_persist_receipt` | a temporary `NOBYPASSRLS`/non-superuser role granted only receipt SELECT/UPDATE can read its own tenant's receipts, sees zero rows under another tenant context, and cannot update hidden cross-tenant rows | Implemented acceptance; hosted execution pending | +| RLS acceptance cannot leak its probe principal on assertion failure | run-unique probe role plus shared `cleanup_probe_role(...)` and the test's existing EXIT trap | failure/abort cleanup is best-effort so it preserves the original assertion failure; the normal success path invokes the same cleanup strictly and fails if role discovery or `DROP OWNED`/`DROP ROLE` fails | Implemented after current-head review finding; exact-head re-review and hosted execution pending | | Receipt state is append-only | append-only row trigger + TRUNCATE trigger | PostgreSQL contract requires same-tenant UPDATE rejection in addition to cross-tenant RLS invisibility | Implemented; hosted execution pending | | Replay state is PII-minimized | receipt stores tenant, opaque key, digests, document identity, database time only | schema inspection; no document bytes, free-form HR values, credentials, compensation, rating, or duplicated Person/Employment columns | Implemented | | Lost-response retry can recover authoritative identity | receipt persists in the same transaction as the document write | first committed result is followed by a separate retry that must return the same stored receipt/result | Implemented; hosted execution pending | @@ -40,6 +41,8 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel - Existing idempotency acceptance repaired to provide tenant context: `4ba85c535626f81468c74f4a5584385b1ad1a883`. - ADR currentization for tenant-bound retry coordination: `db226ed24c00e692340922afb9150721581fe2ab`. - Deterministic concurrency/RLS acceptance repair after review finding: `262122bfe0f959d5225e57bf8de9f9e54018af13`. +- Failure-path RLS probe-role cleanup repair: `4757e24ae03db08c10d93ccf07f6ddbf9fe73a85`. +- Success-path cleanup made fail-closed rather than best-effort: `be064e24280375c7eecb4b9e910f65d1145d434b`. ## Evidence limits From fb8da85bc62df519828c480a3f07b8a44be344ee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 13:04:17 +0900 Subject: [PATCH 23/68] test(document-records): make RLS probe role globally unique --- tests/test_document_record_idempotency_postgres.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_document_record_idempotency_postgres.sh b/tests/test_document_record_idempotency_postgres.sh index 333777c33..4e7169cb0 100644 --- a/tests/test_document_record_idempotency_postgres.sh +++ b/tests/test_document_record_idempotency_postgres.sh @@ -178,7 +178,12 @@ SECOND_OUTPUT="${CONCURRENCY_DIR}/second-output" mkfifo "${FIRST_INPUT}" first_client_pid="" second_client_pid="" -PROBE_ROLE="orgmetra_document_receipt_probe_${BASHPID}" +PROBE_ROLE_SUFFIX="$(python3 - <<'PY' +import uuid +print(uuid.uuid4().hex[:24]) +PY +)" +PROBE_ROLE="orgmetra_document_receipt_probe_${PROBE_ROLE_SUFFIX}" cleanup_probe_role() { local mode="${1:-strict}" @@ -387,4 +392,4 @@ set -e if [[ ${mutation_status} -eq 0 || "${mutation_output}" != *"append-only"* ]]; then echo "document-record idempotency receipt was mutable: ${mutation_output}" >&2 exit 1 -fi +fi \ No newline at end of file From 3b9938902327ab819dc876544252ff9036993fb4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 15:07:56 +0900 Subject: [PATCH 24/68] test(document-records): bind replay to original database time --- ...st_document_record_idempotency_postgres.sh | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/test_document_record_idempotency_postgres.sh b/tests/test_document_record_idempotency_postgres.sh index 4e7169cb0..559fc48ee 100644 --- a/tests/test_document_record_idempotency_postgres.sh +++ b/tests/test_document_record_idempotency_postgres.sh @@ -98,7 +98,8 @@ persist_sql() { SELECT document_record_id::text || '|' || document_record_reference || '|' || audit_event_reference || '|' || outbox_event_reference || '|' || - semantic_command_digest_sha256 || '|' || receipt_digest_sha256 + semantic_command_digest_sha256 || '|' || receipt_digest_sha256 || '|' || + extract(epoch FROM recorded_at)::text FROM public.persist_document_record_once( '${TENANT_ID}'::uuid, '${key}', @@ -152,6 +153,20 @@ if [[ "${counts}" != "1|1" ]]; then exit 1 fi +recorded_at_binding="$(psql "${DATABASE_URL}" -Atqc " +SELECT (persisted.recorded_at = receipt.recorded_at)::text +FROM document_record AS persisted +JOIN document_record_persist_receipt AS receipt + ON receipt.tenant_record_id = persisted.tenant_record_id + AND receipt.document_record_id = persisted.document_record_id +WHERE receipt.tenant_record_id = '${TENANT_ID}'::uuid + AND receipt.idempotency_key = '${IDEMPOTENCY_KEY}'; +")" +if [[ "${recorded_at_binding}" != "true" ]]; then + echo "replay receipt database time diverged from the original committed document time: ${recorded_at_binding}" >&2 + exit 1 +fi + set +e conflict_output="$(with_tenant "${TENANT_ID}" "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -v canonical_evidence="${CANONICAL_EVIDENCE}" \ -c "$(persist_sql "${IDEMPOTENCY_KEY}" "${DOCUMENT_ID}" "${DOCUMENT_REFERENCE}" "${ARTIFACT_REFERENCE}" "${AUDIT_REFERENCE}" "${OUTBOX_REFERENCE}" "${CONFLICTING_APPLICATION_DIGEST}" "${CANONICAL_EVIDENCE}" "${EVIDENCE_DIGEST}")" 2>&1)" @@ -392,4 +407,4 @@ set -e if [[ ${mutation_status} -eq 0 || "${mutation_output}" != *"append-only"* ]]; then echo "document-record idempotency receipt was mutable: ${mutation_output}" >&2 exit 1 -fi \ No newline at end of file +fi From 8692bb11f457dda06b3b5ab1afb3754a135e5825 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 15:08:28 +0900 Subject: [PATCH 25/68] docs(document-records): trace original replay timestamp evidence --- .../document-record-idempotent-persistence.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md index 71c4b5179..1d48275b5 100644 --- a/docs/traceability/document-record-idempotent-persistence.md +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -6,9 +6,9 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | --- | --- | --- | --- | | One retry family has one tenant-scoped opaque key | `document_record_persist_receipt.idempotency_key` in migration 0024 | invalid/different-key behavior is constrained by the migration; #312 review remains pending | Implemented on Draft head | | Session tenant must match requested tenant before retry coordination | explicit `current_tenant_record_id() = p_tenant_record_id` guard before semantic digest/replay lock | `tests/test_document_record_idempotency_tenant_context_postgres.sh` submits a valid tenant-beta command from a tenant-alpha session, requires SQLSTATE-42501 boundary text, and proves zero beta document/receipt rows | Implemented; hosted execution pending Foundation admission | -| Same key + same semantic command returns the first committed result | `persist_document_record_once(...)` semantic digest + replay branch | `tests/test_document_record_idempotency_postgres.sh` compares first and retry result and proves one `document_record` + one receipt while all supported calls set the tenant session context | Implemented; hosted execution pending Foundation admission | +| Same key + same semantic command returns the first committed result | `persist_document_record_once(...)` semantic digest + replay branch | `tests/test_document_record_idempotency_postgres.sh` compares first and retry identity, audit/outbox references, semantic/receipt digests, and the original database-owned `recorded_at` instant; it also proves the durable document and receipt share that same database time while all supported calls set tenant context explicitly | Implemented; hosted execution pending Foundation admission | | Same key + changed semantics fails closed | server-side `orgmetra.document_record_persist_command.v1` SHA-256 | PostgreSQL contract changes only `application_evidence_digest_sha256` and requires the explicit semantic-conflict error | Implemented; hosted execution pending | -| Retry identity is independent of caller session timezone | function-local `SET TimeZone = 'UTC'` for digest construction | same semantic command executes first under UTC, then under Asia/Seoul; result/digest identity must remain identical | Implemented; hosted execution pending | +| Retry identity is independent of caller session timezone | function-local `SET TimeZone = 'UTC'` for digest construction | same semantic command executes first under UTC, then under Asia/Seoul; identity/digest plus the epoch-normalized original `recorded_at` instant must remain identical | Implemented; hosted execution pending | | Unsupported transaction isolation fails closed | `current_setting('transaction_isolation')` guard before command validation/write | `tests/test_document_record_idempotency_isolation_postgres.sh` invokes the owner inside a real `REPEATABLE READ` transaction and requires the explicit isolation error first | Implemented; hosted execution pending | | Concurrent first attempts demonstrably serialize on the owner advisory lock | transaction-scoped `pg_advisory_xact_lock` over tenant + owner namespace + key | the first real PostgreSQL session remains `idle in transaction` after persistence; the second must expose an ungranted `advisory` lock in `pg_locks` and `pg_blocking_pids(...)` must name the first backend before the test permits the first transaction to commit | Implemented acceptance; hosted execution pending | | Replay visibility is explicit | `VOLATILE` function + Read Committed guard | ADR 0309 ties post-lock receipt visibility to PostgreSQL statement snapshots and rejects stronger isolation until a successor algorithm exists | Implemented contract | @@ -18,7 +18,7 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | RLS acceptance cannot leak its probe principal on assertion failure | run-unique probe role plus shared `cleanup_probe_role(...)` and the test's existing EXIT trap | failure/abort cleanup is best-effort so it preserves the original assertion failure; the normal success path invokes the same cleanup strictly and fails if role discovery or `DROP OWNED`/`DROP ROLE` fails | Implemented after current-head review finding; exact-head re-review and hosted execution pending | | Receipt state is append-only | append-only row trigger + TRUNCATE trigger | PostgreSQL contract requires same-tenant UPDATE rejection in addition to cross-tenant RLS invisibility | Implemented; hosted execution pending | | Replay state is PII-minimized | receipt stores tenant, opaque key, digests, document identity, database time only | schema inspection; no document bytes, free-form HR values, credentials, compensation, rating, or duplicated Person/Employment columns | Implemented | -| Lost-response retry can recover authoritative identity | receipt persists in the same transaction as the document write | first committed result is followed by a separate retry that must return the same stored receipt/result | Implemented; hosted execution pending | +| Lost-response retry can recover authoritative identity and time | receipt persists in the same transaction as the document write | first committed result is followed by a separate retry that must return the same stored identity/receipt and original `recorded_at` instant; acceptance also requires `document_record.recorded_at = document_record_persist_receipt.recorded_at` | Implemented; hosted execution pending | | Acceptance connections are closed | test sessions set dedicated `PGAPPNAME` values and are waited before inspection | `pg_stat_activity` must contain zero matching sessions after concurrent acceptance | Implemented; hosted execution pending | | Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310/#311 must discover all three document-record idempotency PostgreSQL contracts; no feature-local workflow is added | Dependency pending stack reconciliation | | Creation/retry receipt is not destruction-completion evidence | ADR 0309 / #308 boundary | #307 dependency order keeps #309/#312 and #308 as distinct prerequisites | Explicitly separated | @@ -43,6 +43,8 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel - Deterministic concurrency/RLS acceptance repair after review finding: `262122bfe0f959d5225e57bf8de9f9e54018af13`. - Failure-path RLS probe-role cleanup repair: `4757e24ae03db08c10d93ccf07f6ddbf9fe73a85`. - Success-path cleanup made fail-closed rather than best-effort: `be064e24280375c7eecb4b9e910f65d1145d434b`. +- Collision-resistant UUID-derived probe-principal identity: `fb8da85bc62df519828c480a3f07b8a44be344ee`. +- Replay-result time binding acceptance: `3b9938902327ab819dc876544252ff9036993fb4`; result comparison now includes epoch-normalized `recorded_at`, and the durable document/receipt database times must match. ## Evidence limits From e55e8076e595f6ae5afcd083bdca58c150d3fa6c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 16:30:49 +0900 Subject: [PATCH 26/68] test(document-records): bind replay time to durable state --- tests/test_document_record_idempotency_postgres.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_document_record_idempotency_postgres.sh b/tests/test_document_record_idempotency_postgres.sh index 559fc48ee..b588f31a7 100644 --- a/tests/test_document_record_idempotency_postgres.sh +++ b/tests/test_document_record_idempotency_postgres.sh @@ -153,8 +153,11 @@ if [[ "${counts}" != "1|1" ]]; then exit 1 fi -recorded_at_binding="$(psql "${DATABASE_URL}" -Atqc " -SELECT (persisted.recorded_at = receipt.recorded_at)::text +returned_recorded_at_epoch="${first_result##*|}" +durable_recorded_at_epochs="$(psql "${DATABASE_URL}" -Atqc " +SELECT + extract(epoch FROM persisted.recorded_at)::text || '|' || + extract(epoch FROM receipt.recorded_at)::text FROM document_record AS persisted JOIN document_record_persist_receipt AS receipt ON receipt.tenant_record_id = persisted.tenant_record_id @@ -162,8 +165,8 @@ JOIN document_record_persist_receipt AS receipt WHERE receipt.tenant_record_id = '${TENANT_ID}'::uuid AND receipt.idempotency_key = '${IDEMPOTENCY_KEY}'; ")" -if [[ "${recorded_at_binding}" != "true" ]]; then - echo "replay receipt database time diverged from the original committed document time: ${recorded_at_binding}" >&2 +if [[ -z "${returned_recorded_at_epoch}" || "${durable_recorded_at_epochs}" != "${returned_recorded_at_epoch}|${returned_recorded_at_epoch}" ]]; then + echo "replay result database time is not bound to the original durable document and receipt time: returned=${returned_recorded_at_epoch} durable=${durable_recorded_at_epochs}" >&2 exit 1 fi From a087a08eb672a35730c406fc2ae79d5ef3b3a94a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 16:31:31 +0900 Subject: [PATCH 27/68] docs(document-records): trace direct replay-time binding --- .../document-record-idempotent-persistence.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md index 1d48275b5..8948e54a2 100644 --- a/docs/traceability/document-record-idempotent-persistence.md +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -6,9 +6,9 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | --- | --- | --- | --- | | One retry family has one tenant-scoped opaque key | `document_record_persist_receipt.idempotency_key` in migration 0024 | invalid/different-key behavior is constrained by the migration; #312 review remains pending | Implemented on Draft head | | Session tenant must match requested tenant before retry coordination | explicit `current_tenant_record_id() = p_tenant_record_id` guard before semantic digest/replay lock | `tests/test_document_record_idempotency_tenant_context_postgres.sh` submits a valid tenant-beta command from a tenant-alpha session, requires SQLSTATE-42501 boundary text, and proves zero beta document/receipt rows | Implemented; hosted execution pending Foundation admission | -| Same key + same semantic command returns the first committed result | `persist_document_record_once(...)` semantic digest + replay branch | `tests/test_document_record_idempotency_postgres.sh` compares first and retry identity, audit/outbox references, semantic/receipt digests, and the original database-owned `recorded_at` instant; it also proves the durable document and receipt share that same database time while all supported calls set tenant context explicitly | Implemented; hosted execution pending Foundation admission | +| Same key + same semantic command returns the first committed result | `persist_document_record_once(...)` semantic digest + replay branch | `tests/test_document_record_idempotency_postgres.sh` compares first and retry identity, audit/outbox references, semantic/receipt digests, and the original database-owned `recorded_at` instant; it then extracts the returned epoch and requires that exact value to equal both durable `document_record.recorded_at` and `document_record_persist_receipt.recorded_at` | Implemented; hosted execution pending Foundation admission | | Same key + changed semantics fails closed | server-side `orgmetra.document_record_persist_command.v1` SHA-256 | PostgreSQL contract changes only `application_evidence_digest_sha256` and requires the explicit semantic-conflict error | Implemented; hosted execution pending | -| Retry identity is independent of caller session timezone | function-local `SET TimeZone = 'UTC'` for digest construction | same semantic command executes first under UTC, then under Asia/Seoul; identity/digest plus the epoch-normalized original `recorded_at` instant must remain identical | Implemented; hosted execution pending | +| Retry identity is independent of caller session timezone | function-local `SET TimeZone = 'UTC'` for digest construction | same semantic command executes first under UTC, then under Asia/Seoul; identity/digest plus the epoch-normalized original `recorded_at` instant must remain identical and bound to durable state | Implemented; hosted execution pending | | Unsupported transaction isolation fails closed | `current_setting('transaction_isolation')` guard before command validation/write | `tests/test_document_record_idempotency_isolation_postgres.sh` invokes the owner inside a real `REPEATABLE READ` transaction and requires the explicit isolation error first | Implemented; hosted execution pending | | Concurrent first attempts demonstrably serialize on the owner advisory lock | transaction-scoped `pg_advisory_xact_lock` over tenant + owner namespace + key | the first real PostgreSQL session remains `idle in transaction` after persistence; the second must expose an ungranted `advisory` lock in `pg_locks` and `pg_blocking_pids(...)` must name the first backend before the test permits the first transaction to commit | Implemented acceptance; hosted execution pending | | Replay visibility is explicit | `VOLATILE` function + Read Committed guard | ADR 0309 ties post-lock receipt visibility to PostgreSQL statement snapshots and rejects stronger isolation until a successor algorithm exists | Implemented contract | @@ -18,7 +18,7 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | RLS acceptance cannot leak its probe principal on assertion failure | run-unique probe role plus shared `cleanup_probe_role(...)` and the test's existing EXIT trap | failure/abort cleanup is best-effort so it preserves the original assertion failure; the normal success path invokes the same cleanup strictly and fails if role discovery or `DROP OWNED`/`DROP ROLE` fails | Implemented after current-head review finding; exact-head re-review and hosted execution pending | | Receipt state is append-only | append-only row trigger + TRUNCATE trigger | PostgreSQL contract requires same-tenant UPDATE rejection in addition to cross-tenant RLS invisibility | Implemented; hosted execution pending | | Replay state is PII-minimized | receipt stores tenant, opaque key, digests, document identity, database time only | schema inspection; no document bytes, free-form HR values, credentials, compensation, rating, or duplicated Person/Employment columns | Implemented | -| Lost-response retry can recover authoritative identity and time | receipt persists in the same transaction as the document write | first committed result is followed by a separate retry that must return the same stored identity/receipt and original `recorded_at` instant; acceptance also requires `document_record.recorded_at = document_record_persist_receipt.recorded_at` | Implemented; hosted execution pending | +| Lost-response retry can recover authoritative identity and time | receipt persists in the same transaction as the document write | first committed result is followed by a separate retry that must return the same stored identity/receipt and original `recorded_at` instant; the returned epoch must equal both durable document and receipt epochs, not merely equal across the two calls | Implemented; hosted execution pending | | Acceptance connections are closed | test sessions set dedicated `PGAPPNAME` values and are waited before inspection | `pg_stat_activity` must contain zero matching sessions after concurrent acceptance | Implemented; hosted execution pending | | Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310/#311 must discover all three document-record idempotency PostgreSQL contracts; no feature-local workflow is added | Dependency pending stack reconciliation | | Creation/retry receipt is not destruction-completion evidence | ADR 0309 / #308 boundary | #307 dependency order keeps #309/#312 and #308 as distinct prerequisites | Explicitly separated | @@ -44,7 +44,8 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel - Failure-path RLS probe-role cleanup repair: `4757e24ae03db08c10d93ccf07f6ddbf9fe73a85`. - Success-path cleanup made fail-closed rather than best-effort: `be064e24280375c7eecb4b9e910f65d1145d434b`. - Collision-resistant UUID-derived probe-principal identity: `fb8da85bc62df519828c480a3f07b8a44be344ee`. -- Replay-result time binding acceptance: `3b9938902327ab819dc876544252ff9036993fb4`; result comparison now includes epoch-normalized `recorded_at`, and the durable document/receipt database times must match. +- Replay-result time-shape acceptance: `3b9938902327ab819dc876544252ff9036993fb4`; result comparison includes epoch-normalized `recorded_at` and durable document/receipt times must match. +- Direct returned-time-to-durable-state binding: `e55e8076e595f6ae5afcd083bdca58c150d3fa6c`; the returned epoch must equal both durable document and receipt epochs, closing the review-identified false-GREEN path. ## Evidence limits From c3aa86c9cd40229948d3497f043c94235ba21981 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 17:00:06 +0900 Subject: [PATCH 28/68] docs(document-records): record uncertain-retry operability --- docs/OPERABILITY.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/OPERABILITY.md b/docs/OPERABILITY.md index 31f3ff23e..fc61b01f0 100644 --- a/docs/OPERABILITY.md +++ b/docs/OPERABILITY.md @@ -41,6 +41,20 @@ - Audit/outbox SQL boundaries pin `search_path` to `pg_catalog, public, pg_temp`, the migration revokes `CREATE` on `public` from `PUBLIC`, and project objects remain in the trusted application schema until schema extraction work explicitly moves them. Normal dispatcher/persistence functions remain security-invoker boundaries; the lost-final-worker recovery function is the sole `SECURITY DEFINER` exception and is owned by the hardened NOLOGIN recovery role rather than a login or superuser role. - Exponential/backoff policy selection, policy-specific producer configuration, and external delivery receipts remain release blockers before reliable asynchronous delivery is called production-ready; terminal dead-letter/escalation evidence and lost-final-worker recovery are implemented but do not by themselves prove downstream receipt. +### Document-record persistence under uncertain retry + +- `persist_document_record_once(...)` is the `document_records` owner boundary for an uncertain-outcome metadata-persistence retry. One transaction binds the immutable `document_record` fact to one append-only `document_record_persist_receipt`; a generic uniqueness violation, elapsed time, or connection loss is never interpreted as success evidence. +- The caller must establish `orgmetra.tenant_record_id` and it must exactly match `p_tenant_record_id` before semantic digest calculation, replay lookup, advisory-lock acquisition, or durable write. RLS remains defense in depth rather than permission to acquire another tenant's database-global coordination state. +- The retry algorithm is intentionally restricted to PostgreSQL `READ COMMITTED`. It relies on a fresh statement snapshot after acquiring the transaction-scoped advisory lock so a waiter can observe the winner's committed receipt. `REPEATABLE READ` and `SERIALIZABLE` calls fail closed rather than pretending that stale-snapshot replay recovery is safe. +- The transaction-scoped advisory lock covers only replay lookup plus authoritative database writes. OCR, LLM work, object-store transfer, network calls, long-running calculation, and other external work must complete before entering this transaction boundary; no idle external wait is allowed while the lock is held. +- The semantic command digest is tenant- and purpose-bound and includes the governed persistence inputs. Function-local UTC serialization prevents equivalent `timestamptz` instants from acquiring different replay identities because of caller session timezone. +- Same tenant/key plus the same semantic digest returns the originally committed document identity, audit/outbox references, semantic digest, receipt digest, and database-owned `recorded_at`. The replay result does not mint fresh authoritative identities or timestamps. Same tenant/key with a different semantic digest fails closed as a conflicting replay. +- After a transport failure where commit outcome is unknown, recovery resubmits the same purpose-bound idempotency key and the exact same semantic command. A new key is a new command identity and must not be used merely to escape uncertainty. Operators escalate a conflicting replay instead of deleting or rewriting the original receipt. +- The receipt is append-only, FORCE-RLS protected, and PII-minimized: it carries the opaque idempotency identity, semantic digest, document identity, receipt digest, and original database time rather than document bytes or free-form HR content. +- Concurrency acceptance must observe the second PostgreSQL backend actually blocked on the first backend's advisory lock and must prove both callers converge to one durable document and one receipt. Fixed sleeps are not serialization evidence. +- Recovery evidence is incomplete if a test client, transaction, or temporary security principal survives the acceptance run. Test-only principals use collision-resistant per-run identities; normal completion verifies strict cleanup, while failure cleanup remains best-effort so it cannot mask the original assertion. +- This retry receipt proves initial metadata persistence only. Return/destruction completion and recovery-invisibility evidence belong to the separate `document_records` lifecycle authority tracked by #308 and must not be inferred from this receipt. + ### Other dependencies - Psychometrics Commons unavailable: assessment-result fetches show an unavailable state, not invented scores. From 06f3fa936f4e0c96b579bf254f8b0976e8d16893 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 17:00:44 +0900 Subject: [PATCH 29/68] docs(document-records): bind idempotency acceptance strategy --- docs/TEST_STRATEGY.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/TEST_STRATEGY.md b/docs/TEST_STRATEGY.md index c20813b72..9ba910d51 100644 --- a/docs/TEST_STRATEGY.md +++ b/docs/TEST_STRATEGY.md @@ -31,12 +31,13 @@ The command runs Python repository-integrity validation, the dependency-free Nod | Predictive-validity study case worker/decision/evidence/criterion and recorded-time integrity | `bash tests/test_validity_study_case_postgres.sh` against PostgreSQL 16 in Foundation CI | | Performance criterion observation Job, cycle, staffing, current-recorded-time, and UTC date-boundary integrity | `bash tests/test_criterion_observation_scope_postgres.sh` against PostgreSQL 16 in Foundation CI | | Governed People mutation idempotency: tenant/route/key uniqueness, identical-command replay, changed-command rejection, rollback safety, append-only/TRUNCATE protection, forced RLS and concurrent exact-key serialization | `bash tests/test_people_mutation_idempotency_postgres.sh` against PostgreSQL 16 in Foundation CI | +| Governed `document_records` uncertain-retry persistence: purpose-bound idempotency receipt, exact semantic replay, conflicting replay rejection, database-owned result-time recovery, real advisory-lock wait graph, FORCE-RLS behavior, tenant-before-lock enforcement, unsupported-isolation fail-close, timezone-stable digest/result identity, connection cleanup, and failure-safe temporary-principal cleanup | `bash tests/test_document_record_idempotency_postgres.sh`, `bash tests/test_document_record_idempotency_tenant_context_postgres.sh`, and `bash tests/test_document_record_idempotency_isolation_postgres.sh` against PostgreSQL 16 through owner-neutral Foundation discovery | | Tenant/actor/purpose authorization matrix and negative high-impact commands | service-specific unit and integration test commands recorded in each service package | | AsyncAPI/CloudEvents envelope compatibility | provider and consumer contract test commands recorded beside the versioned event schema | | External adapter timeout, malformed response, tenant mismatch, and unavailable-state handling | fake-server tests in each adapter package | | Role-workspace keyboard, focus, exact-value, permission-denied, and confirmation states | Storybook interaction/a11y tests plus browser E2E for the owning workspace | -The PostgreSQL scripts apply the checked-in migration chain required by the contract under test to a fresh database. The bitemporal and evidence-sealing tests execute concurrency regressions with an observable database barrier instead of a fixed scheduling assumption. The tenant-isolation test proves both read and write enforcement with unprivileged `NOLOGIN NOBYPASSRLS` roles, so table-owner/superuser bypass cannot manufacture a passing tenant result. The evidence-sealing test compares database output with independently precomputed canonical SHA-256 fixtures and forces a membership transaction to hold the evidence-set row lock before finalization, proving the digest snapshot includes evidence that committed first. The audit/outbox contract stores exact `AuditOutboxEvent.canonical_json()` bytes, independently verifies their SHA-256 digest in PostgreSQL, rejects extra top-level PII fields even when a caller recomputes the digest, and exercises outbox lifecycle invariants separately from immutable audit facts. The outbox-claim contract proves an already-expired lease cannot be created, verifies deterministic tenant-scoped claims return the immutable event/digest while live leases are excluded, then lets a valid one-second lease expire and requires atomic takeover of that same row with attempt count 2, a new future lease, and explicit `lease_expired` evidence. The dead-letter contract applies migrations 0001 through 0007, proves the dispatcher cannot select its own terminal attempt budget, rejects direct terminal DML before matching immutable escalation evidence and the stored budget are satisfied, exercises the real retry/claim path through the database-owned default fifth attempt, rejects retry at attempt five, lets the final lease expire, proves a replacement worker cannot create attempt six, proves the row remains bound to the recorded worker identity, rejects a foreign finalizer, permits that exact recorded identity to append terminal evidence after expiry, and rejects fabricated escalation evidence for nonterminal work. The People mutation idempotency contract applies the authoritative migration chain through 0012, verifies the replay record is written in the same transaction as its authoritative fact and audit/outbox evidence, proves rollback leaves no false replay marker, and uses concurrent exact-key sessions to prove one canonical committed identity wins without duplicate business facts. Foundation CI executes every matrix entry independently; a cancelled, skipped, queued, absent, neutral, failed, stale, predecessor-head, status-only, or model-only matrix result is not database evidence for the current head. +The PostgreSQL scripts apply the checked-in migration chain required by the contract under test to a fresh database. The bitemporal and evidence-sealing tests execute concurrency regressions with an observable database barrier instead of a fixed scheduling assumption. The tenant-isolation test proves both read and write enforcement with unprivileged `NOLOGIN NOBYPASSRLS` roles, so table-owner/superuser bypass cannot manufacture a passing tenant result. The evidence-sealing test compares database output with independently precomputed canonical SHA-256 fixtures and forces a membership transaction to hold the evidence-set row lock before finalization, proving the digest snapshot includes evidence that committed first. The audit/outbox contract stores exact `AuditOutboxEvent.canonical_json()` bytes, independently verifies their SHA-256 digest in PostgreSQL, rejects extra top-level PII fields even when a caller recomputes the digest, and exercises outbox lifecycle invariants separately from immutable audit facts. The outbox-claim contract proves an already-expired lease cannot be created, verifies deterministic tenant-scoped claims return the immutable event/digest while live leases are excluded, then lets a valid one-second lease expire and requires atomic takeover of that same row with attempt count 2, a new future lease, and explicit `lease_expired` evidence. The dead-letter contract applies migrations 0001 through 0007, proves the dispatcher cannot select its own terminal attempt budget, rejects direct terminal DML before matching immutable escalation evidence and the stored budget are satisfied, exercises the real retry/claim path through the database-owned default fifth attempt, rejects retry at attempt five, lets the final lease expire, proves a replacement worker cannot create attempt six, proves the row remains bound to the recorded worker identity, rejects a foreign finalizer, permits that exact recorded identity to append terminal evidence after expiry, and rejects fabricated escalation evidence for nonterminal work. The People mutation idempotency contract applies the authoritative migration chain through 0012, verifies the replay record is written in the same transaction as its authoritative fact and audit/outbox evidence, proves rollback leaves no false replay marker, and uses concurrent exact-key sessions to prove one canonical committed identity wins without duplicate business facts. The document-record idempotency contracts apply the governed document-record chain through migration 0024. They require the requested tenant to match active tenant context before any semantic digest, replay lookup, advisory lock, or write; prove same-key/same-semantic UTC↔Asia/Seoul retries recover the original database-owned `recorded_at`; reject changed semantics; reject `REPEATABLE READ` before command work because replay visibility depends on a fresh post-lock Read Committed statement snapshot; and observe the second backend holding an ungranted advisory lock with `pg_blocking_pids(...)` naming the first before allowing the first transaction to commit. FORCE-RLS is exercised with a temporary `NOBYPASSRLS` non-superuser rather than inferred only from catalog flags. The probe identity is collision-resistant per run, normal-path cleanup is strict, failure cleanup is best-effort so it cannot hide the original assertion, and the concurrency clients must leave no PostgreSQL sessions behind. Foundation admission remains owner-neutral: the contract registry/discovery owner must include these roots without adding a filename switch. Foundation CI executes every matrix entry independently; a cancelled, skipped, queued, absent, neutral, failed, stale, predecessor-head, status-only, or model-only matrix result is not database evidence for the current head. Future service packages must publish their exact test, statement-coverage, branch-coverage, docstring, typecheck, and build commands in the package manifest and CI log. @@ -51,6 +52,9 @@ Required negative and provenance tests include: - a reused confirmation or idempotency key cannot bind to different command content; - an identical tenant/route/idempotency-key retry replays the first committed created-record identity rather than issuing a duplicate authoritative write; - concurrent exact-key requests serialize at the persistence boundary and cannot commit two different identities; +- `document_records` persistence must bind active tenant context before database-global advisory coordination, recover same-key/same-semantic uncertain retries to the exact original receipt/result, and reject same-key changed semantics rather than treating a uniqueness error or elapsed time as success evidence; +- the document-record retry path must fail closed outside Read Committed, and real concurrency acceptance must observe PostgreSQL's advisory wait graph rather than rely on a scheduling delay; +- document-record retry evidence must return the original database-owned time and remain invariant across caller session timezones; test-only RLS principals and concurrent clients must be fully cleaned up on a passing run; - previewed evidence versions must equal recorded evidence versions; - an open evidence set rejects a caller-supplied digest, preventing a client assertion from masquerading as database-observed membership; - finalizing a selection decision requires at least one versioned evidence member, computes the canonical SHA-256 digest in PostgreSQL, and seals exactly one evidence set in the same transaction; From 7a5e06e501e483dd0c8131567a2689f0229d7d89 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 17:05:53 +0900 Subject: [PATCH 30/68] docs(document-records): reseal idempotency owner docs --- manifest.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/manifest.json b/manifest.json index f7b6cf55e..a5d0b2a0e 100644 --- a/manifest.json +++ b/manifest.json @@ -155,9 +155,9 @@ }, { "path": "docs/OPERABILITY.md", - "sha256": "82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62", - "bytes": 11189, - "lines": 71 + "sha256": "591c765b107f84e76b3f380adfafc3fec67c0735e6d3f33431369659f4f28c15", + "bytes": 14597, + "lines": 85 }, { "path": "docs/PRD.md", @@ -185,9 +185,9 @@ }, { "path": "docs/TEST_STRATEGY.md", - "sha256": "d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8", - "bytes": 16534, - "lines": 135 + "sha256": "48596f3258300feedaa42bedc89ac59ef8775a321d323243c1d29ef52e5db37e", + "bytes": 19089, + "lines": 139 }, { "path": "docs/THREAT_MODEL.md", From 3157214ef1dd3b7767b7cc5a918e0b9b06cb7996 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 17:26:53 +0900 Subject: [PATCH 31/68] test(document-records): prove post-commit retry recovery --- ...empotency_postcommit_recovery_companion.sh | 249 ++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 tests/document_record_idempotency_postcommit_recovery_companion.sh diff --git a/tests/document_record_idempotency_postcommit_recovery_companion.sh b/tests/document_record_idempotency_postcommit_recovery_companion.sh new file mode 100644 index 000000000..7663ed829 --- /dev/null +++ b/tests/document_record_idempotency_postcommit_recovery_companion.sh @@ -0,0 +1,249 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" + +for migration in \ + database/migrations/0001_foundation_schema.sql \ + database/migrations/0002_sealed_evidence_digest.sql \ + database/migrations/0021_document_record_persistence.sql \ + database/migrations/0022_document_record_evidence_unique_keys.sql \ + database/migrations/0023_document_record_canonical_encoding.sql \ + database/migrations/0024_document_record_idempotent_persistence.sql; do + if [[ ! -f "${migration}" ]]; then + echo "required document-record post-commit recovery migration is missing: ${migration}" >&2 + exit 1 + fi + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f "${migration}" +done + +TENANT_ID="30000000-0000-7000-8000-000000000003" +DOCUMENT_ID="00000000-0000-7000-8000-000000000231" +DOCUMENT_REFERENCE="document_record:00000000-0000-4000-8000-000000000231" +PERSON_REFERENCE="person_record:00000000-0000-4000-8000-000000000211" +EMPLOYMENT_REFERENCE="employment_record:00000000-0000-4000-8000-000000000221" +UPLOADER="actor:00000000-0000-4000-8000-000000000261" +PERSISTED_BY="actor:00000000-0000-4000-8000-000000000262" +ARTIFACT_REFERENCE="document_artifact:00000000-0000-4000-8000-000000000241" +ARTIFACT_DIGEST="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +SOURCE_DIGEST="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +RETENTION_REFERENCE="retention_policy:00000000-0000-4000-8000-000000000251" +RETENTION_DIGEST="cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" +APPLICATION_DIGEST="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" +AUDIT_REFERENCE="audit_event:00000000-0000-4000-8000-000000000271" +OUTBOX_REFERENCE="outbox_event:00000000-0000-4000-8000-000000000272" +IDEMPOTENCY_KEY="document-record-persist-00000000-0000-4000-8000-000000000201" +APPLICATION_NAME="orgmetra_document_idempotency_lost_response" + +IFS='|' read -r RECEIVED_AT EVIDENCE_RECORDED_AT < <(psql "${DATABASE_URL}" -Atqc " +SELECT + to_char((pg_catalog.transaction_timestamp() - interval '2 minutes') AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"'), + to_char((pg_catalog.transaction_timestamp() - interval '1 minute') AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"'); +") +export RECEIVED_AT EVIDENCE_RECORDED_AT + +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 </dev/null 2>&1 || true + fi + if [[ -n "${client_pid}" ]] && kill -0 "${client_pid}" 2>/dev/null; then + kill "${client_pid}" 2>/dev/null || true + wait "${client_pid}" 2>/dev/null || true + fi + rm -rf "${RECOVERY_DIR}" +} +trap cleanup EXIT + +# One simple-query batch commits the authoritative write and then remains inside +# pg_sleep. The supervising recovery actor never receives the function result. +# Terminating the backend only after another session can observe the receipt +# proves that the durable first result exists while the original connection +# ultimately reports failure to its caller. +PGOPTIONS="-c orgmetra.tenant_record_id=${TENANT_ID}" \ +PGAPPNAME="${APPLICATION_NAME}" \ +psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 \ + -v canonical_evidence="${CANONICAL_EVIDENCE}" \ + -c "BEGIN; ${PERSIST_SQL} COMMIT; SELECT pg_catalog.pg_sleep(30);" \ + >/dev/null 2>"${CLIENT_ERROR}" & +client_pid=$! + +first_result_durable=false +recovery_deadline=$((SECONDS + 10)) +while (( SECONDS < recovery_deadline )); do + activity_row="$(psql "${DATABASE_URL}" -Atqc " +SELECT + activity.pid::text || '|' || activity.state || '|' || COALESCE(activity.wait_event, '') || '|' || + ( + SELECT count(*)::text + FROM public.document_record_persist_receipt AS receipt + WHERE receipt.tenant_record_id = '${TENANT_ID}'::uuid + AND receipt.idempotency_key = '${IDEMPOTENCY_KEY}' + ) +FROM pg_catalog.pg_stat_activity AS activity +WHERE activity.application_name = '${APPLICATION_NAME}'; +" | head -n 1)" + observed_pid="" + observed_state="" + observed_wait="" + receipt_count="" + if [[ -n "${activity_row}" ]]; then + IFS='|' read -r observed_pid observed_state observed_wait receipt_count <<<"${activity_row}" + fi + if [[ "${observed_pid}" =~ ^[0-9]+$ && "${observed_state}" == "active" && "${observed_wait}" == "PgSleep" && "${receipt_count}" == "1" ]]; then + backend_pid="${observed_pid}" + first_result_durable=true + break + fi + if ! kill -0 "${client_pid}" 2>/dev/null; then + break + fi + sleep 0.05 +done + +if [[ "${first_result_durable}" != "true" ]]; then + echo "post-commit recovery fixture never exposed a durable receipt while the original connection remained live" >&2 + cat "${CLIENT_ERROR}" >&2 || true + exit 1 +fi + +terminate_result="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 \ + -c "SELECT pg_catalog.pg_terminate_backend(${backend_pid});")" +if [[ "${terminate_result}" != "t" ]]; then + echo "could not terminate the committed original persistence connection: ${terminate_result}" >&2 + exit 1 +fi +backend_pid="" + +set +e +wait "${client_pid}" +client_status=$? +set -e +client_pid="" +if [[ ${client_status} -eq 0 ]]; then + echo "original persistence client unexpectedly reported success after forced post-commit connection termination" >&2 + exit 1 +fi + +DURABLE_RESULT="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -c " +SELECT + persisted.document_record_id::text || '|' || persisted.document_record_reference || '|' || + persisted.audit_event_reference || '|' || persisted.outbox_event_reference || '|' || + receipt.semantic_command_digest_sha256 || '|' || receipt.receipt_digest_sha256 || '|' || + extract(epoch FROM persisted.recorded_at)::text +FROM public.document_record_persist_receipt AS receipt +JOIN public.document_record AS persisted + ON persisted.tenant_record_id = receipt.tenant_record_id + AND persisted.document_record_id = receipt.document_record_id +WHERE receipt.tenant_record_id = '${TENANT_ID}'::uuid + AND receipt.idempotency_key = '${IDEMPOTENCY_KEY}'; +")" +if [[ -z "${DURABLE_RESULT}" ]]; then + echo "forced connection loss erased or hid the committed authoritative result" >&2 + exit 1 +fi + +RETRY_RESULT="$(PGOPTIONS="-c orgmetra.tenant_record_id=${TENANT_ID}" \ + psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 \ + -v canonical_evidence="${CANONICAL_EVIDENCE}" -c "${PERSIST_SQL}")" +if [[ "${RETRY_RESULT}" != "${DURABLE_RESULT}" ]]; then + echo "same-command retry did not recover the exact durable result after post-commit connection loss: durable=${DURABLE_RESULT} retry=${RETRY_RESULT}" >&2 + exit 1 +fi + +recovery_counts="$(psql "${DATABASE_URL}" -Atqc " +SELECT + (SELECT count(*) FROM public.document_record WHERE tenant_record_id = '${TENANT_ID}'::uuid AND document_record_id = '${DOCUMENT_ID}'::uuid)::text + || '|' || + (SELECT count(*) FROM public.document_record_persist_receipt WHERE tenant_record_id = '${TENANT_ID}'::uuid AND idempotency_key = '${IDEMPOTENCY_KEY}')::text; +")" +if [[ "${recovery_counts}" != "1|1" ]]; then + echo "post-commit retry duplicated durable document or receipt state: ${recovery_counts}" >&2 + exit 1 +fi + +active_recovery_connections="$(psql "${DATABASE_URL}" -Atqc " +SELECT count(*) +FROM pg_catalog.pg_stat_activity +WHERE application_name = '${APPLICATION_NAME}'; +")" +if [[ "${active_recovery_connections}" != "0" ]]; then + echo "post-commit recovery acceptance leaked PostgreSQL connections: ${active_recovery_connections}" >&2 + exit 1 +fi From 3fcda9a41a770829d071f1af1917e1d11611d555 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 17:27:20 +0900 Subject: [PATCH 32/68] docs(document-records): trace post-commit recovery acceptance --- .../document-record-idempotent-persistence.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md index 8948e54a2..4e9ae1dbc 100644 --- a/docs/traceability/document-record-idempotent-persistence.md +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -18,9 +18,9 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | RLS acceptance cannot leak its probe principal on assertion failure | run-unique probe role plus shared `cleanup_probe_role(...)` and the test's existing EXIT trap | failure/abort cleanup is best-effort so it preserves the original assertion failure; the normal success path invokes the same cleanup strictly and fails if role discovery or `DROP OWNED`/`DROP ROLE` fails | Implemented after current-head review finding; exact-head re-review and hosted execution pending | | Receipt state is append-only | append-only row trigger + TRUNCATE trigger | PostgreSQL contract requires same-tenant UPDATE rejection in addition to cross-tenant RLS invisibility | Implemented; hosted execution pending | | Replay state is PII-minimized | receipt stores tenant, opaque key, digests, document identity, database time only | schema inspection; no document bytes, free-form HR values, credentials, compensation, rating, or duplicated Person/Employment columns | Implemented | -| Lost-response retry can recover authoritative identity and time | receipt persists in the same transaction as the document write | first committed result is followed by a separate retry that must return the same stored identity/receipt and original `recorded_at` instant; the returned epoch must equal both durable document and receipt epochs, not merely equal across the two calls | Implemented; hosted execution pending | -| Acceptance connections are closed | test sessions set dedicated `PGAPPNAME` values and are waited before inspection | `pg_stat_activity` must contain zero matching sessions after concurrent acceptance | Implemented; hosted execution pending | -| Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310/#311 must discover all three document-record idempotency PostgreSQL contracts; no feature-local workflow is added | Dependency pending stack reconciliation | +| Lost-response retry can recover authoritative identity and time | receipt persists in the same transaction as the document write | `tests/document_record_idempotency_postcommit_recovery_companion.sh` submits one simple-query batch that commits the owner write and then blocks in `pg_sleep`; a supervisory connection waits until the receipt is externally visible, terminates that committed backend, requires the original client to fail, and retries the exact same semantic command from a new connection. The retry must equal the durable document/receipt identity, digests, references, and original database-owned time with one document + one receipt only | Implemented executable companion; hosted execution pending Foundation admission | +| Acceptance connections are closed | test sessions set dedicated `PGAPPNAME` values and are waited before inspection | both the concurrent root contract and post-commit recovery companion require `pg_stat_activity` to contain zero matching sessions after acceptance | Implemented; hosted execution pending | +| Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310/#311 must discover all three document-record idempotency PostgreSQL roots and execute the post-commit recovery script as an explicit reviewed companion; no feature-local workflow or fourth filename-specific root is added | Dependency pending stack reconciliation | | Creation/retry receipt is not destruction-completion evidence | ADR 0309 / #308 boundary | #307 dependency order keeps #309/#312 and #308 as distinct prerequisites | Explicitly separated | ## Evidence lineage @@ -46,11 +46,12 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel - Collision-resistant UUID-derived probe-principal identity: `fb8da85bc62df519828c480a3f07b8a44be344ee`. - Replay-result time-shape acceptance: `3b9938902327ab819dc876544252ff9036993fb4`; result comparison includes epoch-normalized `recorded_at` and durable document/receipt times must match. - Direct returned-time-to-durable-state binding: `e55e8076e595f6ae5afcd083bdca58c150d3fa6c`; the returned epoch must equal both durable document and receipt epochs, closing the review-identified false-GREEN path. +- Real post-commit connection-loss recovery companion: `3157214ef1dd3b7767b7cc5a918e0b9b06cb7996`; the original simple-query batch commits and is then terminated while blocked after commit, its client must fail, and a new connection must recover the exact durable first result without duplication. ## Evidence limits -No hosted PostgreSQL execution is claimed on the current stacked branch. #312 targets #107, while the canonical PostgreSQL Foundation implementation is separately stacked under #259/#311. Exact-head GREEN requires ordinary-forward reconciliation of those histories and a fresh run that discovers all three contracts without filename-specific workflow logic. +No hosted PostgreSQL execution is claimed on the current stacked branch. #312 targets #107, while the canonical PostgreSQL Foundation implementation is separately stacked under #259/#311. Exact-head GREEN requires ordinary-forward reconciliation of those histories and a fresh run that discovers all three roots plus the reviewed post-commit recovery companion without filename-specific workflow logic. -The bounded observation loops in the concurrency test only wait for PostgreSQL's explicit `pg_stat_activity`/`pg_locks` state. They do not use elapsed time as evidence that serialization happened: the acceptance fails unless the second backend is actually shown waiting on the first backend's advisory lock. +The bounded observation loops in the concurrency and post-commit-recovery acceptance only wait for explicit PostgreSQL state. They do not use elapsed time as evidence that serialization or commit happened: concurrency fails unless the second backend is actually shown waiting on the first backend's advisory lock; post-commit recovery fails unless another session can read the durable receipt while the original backend is still alive in `PgSleep` before that backend is terminated. -CodeRabbit/Devin status is review evidence only. It is not a substitute for the PostgreSQL runtime contracts, required protected-branch gates, or a qualifying independent approval. +CodeRabbit/Devin status is review evidence only. It is not a substitute for the PostgreSQL runtime contracts, required protected-branch gates, or a qualifying independent approval. \ No newline at end of file From cb587679768eeb62712ca529f30d7a630817d5bd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 17:28:56 +0900 Subject: [PATCH 33/68] test(document-records): run recovery after owner root --- ...empotency_postcommit_recovery_companion.sh | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/tests/document_record_idempotency_postcommit_recovery_companion.sh b/tests/document_record_idempotency_postcommit_recovery_companion.sh index 7663ed829..1e2d3fc15 100644 --- a/tests/document_record_idempotency_postcommit_recovery_companion.sh +++ b/tests/document_record_idempotency_postcommit_recovery_companion.sh @@ -3,19 +3,21 @@ set -euo pipefail : "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" -for migration in \ - database/migrations/0001_foundation_schema.sql \ - database/migrations/0002_sealed_evidence_digest.sql \ - database/migrations/0021_document_record_persistence.sql \ - database/migrations/0022_document_record_evidence_unique_keys.sql \ - database/migrations/0023_document_record_canonical_encoding.sql \ - database/migrations/0024_document_record_idempotent_persistence.sql; do - if [[ ! -f "${migration}" ]]; then - echo "required document-record post-commit recovery migration is missing: ${migration}" >&2 - exit 1 - fi - psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f "${migration}" -done +required_owner_state="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -c " +SELECT ( + pg_catalog.to_regclass('public.document_record_persist_receipt') IS NOT NULL + AND EXISTS ( + SELECT 1 + FROM pg_catalog.pg_proc + WHERE pronamespace = 'public'::pg_catalog.regnamespace + AND proname = 'persist_document_record_once' + ) +)::text; +")" +if [[ "${required_owner_state}" != "true" ]]; then + echo "post-commit recovery companion requires the document-record idempotency root contract to run first" >&2 + exit 1 +fi TENANT_ID="30000000-0000-7000-8000-000000000003" DOCUMENT_ID="00000000-0000-7000-8000-000000000231" From 4535d9fea7b2b995be627a23316a65474d714957 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 17:29:52 +0900 Subject: [PATCH 34/68] docs(document-records): trace companion execution order --- .../traceability/document-record-idempotent-persistence.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md index 4e9ae1dbc..5870d3cac 100644 --- a/docs/traceability/document-record-idempotent-persistence.md +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -18,9 +18,9 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | RLS acceptance cannot leak its probe principal on assertion failure | run-unique probe role plus shared `cleanup_probe_role(...)` and the test's existing EXIT trap | failure/abort cleanup is best-effort so it preserves the original assertion failure; the normal success path invokes the same cleanup strictly and fails if role discovery or `DROP OWNED`/`DROP ROLE` fails | Implemented after current-head review finding; exact-head re-review and hosted execution pending | | Receipt state is append-only | append-only row trigger + TRUNCATE trigger | PostgreSQL contract requires same-tenant UPDATE rejection in addition to cross-tenant RLS invisibility | Implemented; hosted execution pending | | Replay state is PII-minimized | receipt stores tenant, opaque key, digests, document identity, database time only | schema inspection; no document bytes, free-form HR values, credentials, compensation, rating, or duplicated Person/Employment columns | Implemented | -| Lost-response retry can recover authoritative identity and time | receipt persists in the same transaction as the document write | `tests/document_record_idempotency_postcommit_recovery_companion.sh` submits one simple-query batch that commits the owner write and then blocks in `pg_sleep`; a supervisory connection waits until the receipt is externally visible, terminates that committed backend, requires the original client to fail, and retries the exact same semantic command from a new connection. The retry must equal the durable document/receipt identity, digests, references, and original database-owned time with one document + one receipt only | Implemented executable companion; hosted execution pending Foundation admission | +| Lost-response retry can recover authoritative identity and time | receipt persists in the same transaction as the document write | `tests/document_record_idempotency_postcommit_recovery_companion.sh` runs after the main idempotency root has established migration state, submits one simple-query batch that commits the owner write and then blocks in `pg_sleep`; a supervisory connection waits until the receipt is externally visible, terminates that committed backend, requires the original client to fail, and retries the exact same semantic command from a new connection. The retry must equal the durable document/receipt identity, digests, references, and original database-owned time with one document + one receipt only | Implemented executable companion; hosted execution pending Foundation admission | | Acceptance connections are closed | test sessions set dedicated `PGAPPNAME` values and are waited before inspection | both the concurrent root contract and post-commit recovery companion require `pg_stat_activity` to contain zero matching sessions after acceptance | Implemented; hosted execution pending | -| Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310/#311 must discover all three document-record idempotency PostgreSQL roots and execute the post-commit recovery script as an explicit reviewed companion; no feature-local workflow or fourth filename-specific root is added | Dependency pending stack reconciliation | +| Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310/#311 must discover all three document-record idempotency PostgreSQL roots and execute the post-commit recovery script as an explicit reviewed companion of the main idempotency root; no feature-local workflow or fourth filename-specific root is added | Dependency pending stack reconciliation | | Creation/retry receipt is not destruction-completion evidence | ADR 0309 / #308 boundary | #307 dependency order keeps #309/#312 and #308 as distinct prerequisites | Explicitly separated | ## Evidence lineage @@ -46,7 +46,8 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel - Collision-resistant UUID-derived probe-principal identity: `fb8da85bc62df519828c480a3f07b8a44be344ee`. - Replay-result time-shape acceptance: `3b9938902327ab819dc876544252ff9036993fb4`; result comparison includes epoch-normalized `recorded_at` and durable document/receipt times must match. - Direct returned-time-to-durable-state binding: `e55e8076e595f6ae5afcd083bdca58c150d3fa6c`; the returned epoch must equal both durable document and receipt epochs, closing the review-identified false-GREEN path. -- Real post-commit connection-loss recovery companion: `3157214ef1dd3b7767b7cc5a918e0b9b06cb7996`; the original simple-query batch commits and is then terminated while blocked after commit, its client must fail, and a new connection must recover the exact durable first result without duplication. +- Initial post-commit connection-loss recovery companion: `3157214ef1dd3b7767b7cc5a918e0b9b06cb7996`; it introduced the real backend-termination/retry scenario. +- Companion execution-order repair: `cb587679768eeb62712ca529f30d7a630817d5bd`; the companion no longer reapplies migrations and instead fails closed unless the main idempotency root has already established the owner schema/function, matching Foundation root→companion semantics. ## Evidence limits From 626c62e53dfb9a4a92ad711d19699c6cb922a2ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:08:34 +0900 Subject: [PATCH 35/68] test(document-records): bind recovery backend identity --- ...dempotency_postcommit_recovery_contract.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/test_document_record_idempotency_postcommit_recovery_contract.py diff --git a/tests/test_document_record_idempotency_postcommit_recovery_contract.py b/tests/test_document_record_idempotency_postcommit_recovery_contract.py new file mode 100644 index 000000000..43215e04c --- /dev/null +++ b/tests/test_document_record_idempotency_postcommit_recovery_contract.py @@ -0,0 +1,37 @@ +"""Regression contracts for post-commit document-record recovery acceptance.""" + +from pathlib import Path + + +COMPANION = Path(__file__).with_name( + "document_record_idempotency_postcommit_recovery_companion.sh" +) + + +def _companion_source() -> str: + return COMPANION.read_text(encoding="utf-8") + + +def test_recovery_termination_binds_checked_backend_identity() -> None: + """Termination must consume the same backend identity that observation accepted.""" + + source = _companion_source() + + assert "backend_start_epoch" in source + assert "extract(epoch FROM activity.backend_start)::text" in source + assert "terminate_captured_backend" in source + assert "application_name = '${APPLICATION_NAME}'" in source + assert "extract(epoch FROM backend_start)::text = '${backend_start_epoch}'" in source + assert '[[ "${termination_receipt}" == "1|true" ]]' in source + + +def test_recovery_session_name_is_execution_unique() -> None: + """Concurrent or leaked acceptance sessions must not share one global marker.""" + + source = _companion_source() + + assert "uuid.uuid4().hex[:24]" in source + assert ( + 'APPLICATION_NAME="orgmetra_document_idempotency_lost_${APPLICATION_SUFFIX}"' + in source + ) From 8a485d60514e1a69124294c6656ffae0bb5d4f98 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:09:11 +0900 Subject: [PATCH 36/68] fix(document-records): bind recovery backend termination identity --- ...empotency_postcommit_recovery_companion.sh | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/tests/document_record_idempotency_postcommit_recovery_companion.sh b/tests/document_record_idempotency_postcommit_recovery_companion.sh index 1e2d3fc15..f991e5846 100644 --- a/tests/document_record_idempotency_postcommit_recovery_companion.sh +++ b/tests/document_record_idempotency_postcommit_recovery_companion.sh @@ -35,7 +35,13 @@ APPLICATION_DIGEST="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee AUDIT_REFERENCE="audit_event:00000000-0000-4000-8000-000000000271" OUTBOX_REFERENCE="outbox_event:00000000-0000-4000-8000-000000000272" IDEMPOTENCY_KEY="document-record-persist-00000000-0000-4000-8000-000000000201" -APPLICATION_NAME="orgmetra_document_idempotency_lost_response" +APPLICATION_SUFFIX="$(python3 - <<'PY' +import uuid + +print(uuid.uuid4().hex[:24]) +PY +)" +APPLICATION_NAME="orgmetra_document_idempotency_lost_${APPLICATION_SUFFIX}" IFS='|' read -r RECEIVED_AT EVIDENCE_RECORDED_AT < <(psql "${DATABASE_URL}" -Atqc " SELECT @@ -118,12 +124,30 @@ RECOVERY_DIR="$(mktemp -d)" CLIENT_ERROR="${RECOVERY_DIR}/lost-response-client.err" client_pid="" backend_pid="" +backend_start_epoch="" + +terminate_captured_backend() { + local termination_receipt + + if [[ ! "${backend_pid}" =~ ^[0-9]+$ || ! "${backend_start_epoch}" =~ ^[0-9]+([.][0-9]+)?$ ]]; then + return 1 + fi + + termination_receipt="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -c " +SELECT count(*)::text || '|' || + COALESCE(pg_catalog.bool_and(pg_catalog.pg_terminate_backend(pid)), false)::text +FROM pg_catalog.pg_stat_activity +WHERE pid = ${backend_pid} + AND application_name = '${APPLICATION_NAME}' + AND extract(epoch FROM backend_start)::text = '${backend_start_epoch}'; +")" || return 1 + + [[ "${termination_receipt}" == "1|true" ]] +} cleanup() { - if [[ "${backend_pid}" =~ ^[0-9]+$ ]]; then - psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 \ - -c "SELECT pg_catalog.pg_terminate_backend(${backend_pid}) WHERE EXISTS (SELECT 1 FROM pg_catalog.pg_stat_activity WHERE pid = ${backend_pid});" \ - >/dev/null 2>&1 || true + if [[ "${backend_pid}" =~ ^[0-9]+$ && "${backend_start_epoch}" =~ ^[0-9]+([.][0-9]+)?$ ]]; then + terminate_captured_backend >/dev/null 2>&1 || true fi if [[ -n "${client_pid}" ]] && kill -0 "${client_pid}" 2>/dev/null; then kill "${client_pid}" 2>/dev/null || true @@ -151,7 +175,8 @@ recovery_deadline=$((SECONDS + 10)) while (( SECONDS < recovery_deadline )); do activity_row="$(psql "${DATABASE_URL}" -Atqc " SELECT - activity.pid::text || '|' || activity.state || '|' || COALESCE(activity.wait_event, '') || '|' || + activity.pid::text || '|' || extract(epoch FROM activity.backend_start)::text || '|' || + activity.state || '|' || COALESCE(activity.wait_event, '') || '|' || ( SELECT count(*)::text FROM public.document_record_persist_receipt AS receipt @@ -162,14 +187,16 @@ FROM pg_catalog.pg_stat_activity AS activity WHERE activity.application_name = '${APPLICATION_NAME}'; " | head -n 1)" observed_pid="" + observed_backend_start_epoch="" observed_state="" observed_wait="" receipt_count="" if [[ -n "${activity_row}" ]]; then - IFS='|' read -r observed_pid observed_state observed_wait receipt_count <<<"${activity_row}" + IFS='|' read -r observed_pid observed_backend_start_epoch observed_state observed_wait receipt_count <<<"${activity_row}" fi - if [[ "${observed_pid}" =~ ^[0-9]+$ && "${observed_state}" == "active" && "${observed_wait}" == "PgSleep" && "${receipt_count}" == "1" ]]; then + if [[ "${observed_pid}" =~ ^[0-9]+$ && "${observed_backend_start_epoch}" =~ ^[0-9]+([.][0-9]+)?$ && "${observed_state}" == "active" && "${observed_wait}" == "PgSleep" && "${receipt_count}" == "1" ]]; then backend_pid="${observed_pid}" + backend_start_epoch="${observed_backend_start_epoch}" first_result_durable=true break fi @@ -185,13 +212,12 @@ if [[ "${first_result_durable}" != "true" ]]; then exit 1 fi -terminate_result="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 \ - -c "SELECT pg_catalog.pg_terminate_backend(${backend_pid});")" -if [[ "${terminate_result}" != "t" ]]; then - echo "could not terminate the committed original persistence connection: ${terminate_result}" >&2 +if ! terminate_captured_backend; then + echo "captured persistence backend identity changed or could not be terminated safely" >&2 exit 1 fi backend_pid="" +backend_start_epoch="" set +e wait "${client_pid}" From ebe5ec1c297eb48f95363aff5123163b09b10430 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:09:59 +0900 Subject: [PATCH 37/68] docs(document-records): trace recovery backend identity binding --- .../document-record-idempotent-persistence.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md index 5870d3cac..987e03c62 100644 --- a/docs/traceability/document-record-idempotent-persistence.md +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -18,8 +18,9 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | RLS acceptance cannot leak its probe principal on assertion failure | run-unique probe role plus shared `cleanup_probe_role(...)` and the test's existing EXIT trap | failure/abort cleanup is best-effort so it preserves the original assertion failure; the normal success path invokes the same cleanup strictly and fails if role discovery or `DROP OWNED`/`DROP ROLE` fails | Implemented after current-head review finding; exact-head re-review and hosted execution pending | | Receipt state is append-only | append-only row trigger + TRUNCATE trigger | PostgreSQL contract requires same-tenant UPDATE rejection in addition to cross-tenant RLS invisibility | Implemented; hosted execution pending | | Replay state is PII-minimized | receipt stores tenant, opaque key, digests, document identity, database time only | schema inspection; no document bytes, free-form HR values, credentials, compensation, rating, or duplicated Person/Employment columns | Implemented | -| Lost-response retry can recover authoritative identity and time | receipt persists in the same transaction as the document write | `tests/document_record_idempotency_postcommit_recovery_companion.sh` runs after the main idempotency root has established migration state, submits one simple-query batch that commits the owner write and then blocks in `pg_sleep`; a supervisory connection waits until the receipt is externally visible, terminates that committed backend, requires the original client to fail, and retries the exact same semantic command from a new connection. The retry must equal the durable document/receipt identity, digests, references, and original database-owned time with one document + one receipt only | Implemented executable companion; hosted execution pending Foundation admission | -| Acceptance connections are closed | test sessions set dedicated `PGAPPNAME` values and are waited before inspection | both the concurrent root contract and post-commit recovery companion require `pg_stat_activity` to contain zero matching sessions after acceptance | Implemented; hosted execution pending | +| Lost-response retry can recover authoritative identity and time | receipt persists in the same transaction as the document write | `tests/document_record_idempotency_postcommit_recovery_companion.sh` runs after the main idempotency root has established migration state, submits one simple-query batch that commits the owner write and then blocks in `pg_sleep`; a supervisory connection waits until the receipt is externally visible, captures the exact backend `(pid, application_name, backend_start)` identity, terminates only that still-matching committed backend, requires the original client to fail, and retries the exact semantic command from a new connection. The retry must equal the durable document/receipt identity, digests, references, and original database-owned time with one document + one receipt only | Implemented executable companion; hosted execution pending Foundation admission | +| Recovery termination cannot target a reused or foreign backend | per-execution UUID-derived `application_name` plus captured `pid` and `backend_start` | `tests/test_document_record_idempotency_postcommit_recovery_contract.py` requires the companion to capture `backend_start`, use an execution-unique session marker, and accept termination only when the same `(pid, application_name, backend_start)` still exists and `pg_terminate_backend` yields exact `1|true`; EXIT cleanup uses the same guarded helper | Implemented source contract; PostgreSQL execution pending Foundation admission | +| Acceptance connections are closed | test sessions use execution-scoped `PGAPPNAME` values and are waited before inspection | both the concurrent root contract and post-commit recovery companion require `pg_stat_activity` to contain zero matching sessions after acceptance | Implemented; hosted execution pending | | Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310/#311 must discover all three document-record idempotency PostgreSQL roots and execute the post-commit recovery script as an explicit reviewed companion of the main idempotency root; no feature-local workflow or fourth filename-specific root is added | Dependency pending stack reconciliation | | Creation/retry receipt is not destruction-completion evidence | ADR 0309 / #308 boundary | #307 dependency order keeps #309/#312 and #308 as distinct prerequisites | Explicitly separated | @@ -48,11 +49,13 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel - Direct returned-time-to-durable-state binding: `e55e8076e595f6ae5afcd083bdca58c150d3fa6c`; the returned epoch must equal both durable document and receipt epochs, closing the review-identified false-GREEN path. - Initial post-commit connection-loss recovery companion: `3157214ef1dd3b7767b7cc5a918e0b9b06cb7996`; it introduced the real backend-termination/retry scenario. - Companion execution-order repair: `cb587679768eeb62712ca529f30d7a630817d5bd`; the companion no longer reapplies migrations and instead fails closed unless the main idempotency root has already established the owner schema/function, matching Foundation root→companion semantics. +- Backend-identity RED contract: `626c62e53dfb9a4a92ad711d19699c6cb922a2ba`; the prior companion terminated by captured PID alone and used one fixed application marker, so checked backend identity was not bound through use. +- Backend-identity causal repair: `8a485d60514e1a69124294c6656ffae0bb5d4f98`; recovery sessions now use UUID-derived markers, capture `backend_start`, and terminate/cleanup only an exact still-live `(pid, application_name, backend_start)` match with a `1|true` receipt. ## Evidence limits No hosted PostgreSQL execution is claimed on the current stacked branch. #312 targets #107, while the canonical PostgreSQL Foundation implementation is separately stacked under #259/#311. Exact-head GREEN requires ordinary-forward reconciliation of those histories and a fresh run that discovers all three roots plus the reviewed post-commit recovery companion without filename-specific workflow logic. -The bounded observation loops in the concurrency and post-commit-recovery acceptance only wait for explicit PostgreSQL state. They do not use elapsed time as evidence that serialization or commit happened: concurrency fails unless the second backend is actually shown waiting on the first backend's advisory lock; post-commit recovery fails unless another session can read the durable receipt while the original backend is still alive in `PgSleep` before that backend is terminated. +The bounded observation loops in the concurrency and post-commit-recovery acceptance only wait for explicit PostgreSQL state. They do not use elapsed time as evidence that serialization or commit happened: concurrency fails unless the second backend is actually shown waiting on the first backend's advisory lock; post-commit recovery fails unless another session can read the durable receipt while the original backend is still alive in `PgSleep` before that exact captured backend identity is terminated. -CodeRabbit/Devin status is review evidence only. It is not a substitute for the PostgreSQL runtime contracts, required protected-branch gates, or a qualifying independent approval. \ No newline at end of file +CodeRabbit/Devin status is review evidence only. It is not a substitute for the PostgreSQL runtime contracts, required protected-branch gates, or a qualifying independent approval. From 8dc8527a5e85920798f49609546892ff1c03eee1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:13:25 +0900 Subject: [PATCH 38/68] test(document-records): protect guarded recovery cleanup --- ...idempotency_postcommit_recovery_contract.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_document_record_idempotency_postcommit_recovery_contract.py b/tests/test_document_record_idempotency_postcommit_recovery_contract.py index 43215e04c..b04cbc4d2 100644 --- a/tests/test_document_record_idempotency_postcommit_recovery_contract.py +++ b/tests/test_document_record_idempotency_postcommit_recovery_contract.py @@ -12,6 +12,12 @@ def _companion_source() -> str: return COMPANION.read_text(encoding="utf-8") +def _shell_function(source: str, name: str, following_marker: str) -> str: + start = source.index(f"{name}() {{") + end = source.index(following_marker, start) + return source[start:end] + + def test_recovery_termination_binds_checked_backend_identity() -> None: """Termination must consume the same backend identity that observation accepted.""" @@ -25,6 +31,18 @@ def test_recovery_termination_binds_checked_backend_identity() -> None: assert '[[ "${termination_receipt}" == "1|true" ]]' in source +def test_exit_cleanup_uses_the_same_guarded_backend_identity() -> None: + """Abort cleanup must not regress to PID-only backend termination.""" + + source = _companion_source() + cleanup = _shell_function(source, "cleanup", "\n}\ntrap cleanup EXIT") + + assert "terminate_captured_backend" in cleanup + assert "pg_terminate_backend" not in cleanup + assert "backend_start_epoch" in cleanup + assert "|| true" in cleanup + + def test_recovery_session_name_is_execution_unique() -> None: """Concurrent or leaked acceptance sessions must not share one global marker.""" From eb99d8e93741215ba3940fc616ab8b875bc7b3bf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:17:08 +0900 Subject: [PATCH 39/68] test(document-records): bind guarded termination helper --- ...dempotency_postcommit_recovery_contract.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/test_document_record_idempotency_postcommit_recovery_contract.py b/tests/test_document_record_idempotency_postcommit_recovery_contract.py index b04cbc4d2..548b4e6f0 100644 --- a/tests/test_document_record_idempotency_postcommit_recovery_contract.py +++ b/tests/test_document_record_idempotency_postcommit_recovery_contract.py @@ -22,13 +22,23 @@ def test_recovery_termination_binds_checked_backend_identity() -> None: """Termination must consume the same backend identity that observation accepted.""" source = _companion_source() + termination = _shell_function( + source, + "terminate_captured_backend", + "\n}\n\ncleanup()", + ) + + assert "WHERE pid = ${backend_pid}" in termination + assert "application_name = '${APPLICATION_NAME}'" in termination + assert ( + "extract(epoch FROM backend_start)::text = '${backend_start_epoch}'" + in termination + ) + assert termination.count("pg_catalog.pg_terminate_backend(pid)") == 1 + assert "pg_terminate_backend(${backend_pid})" not in termination + assert '[[ "${termination_receipt}" == "1|true" ]]' in termination - assert "backend_start_epoch" in source assert "extract(epoch FROM activity.backend_start)::text" in source - assert "terminate_captured_backend" in source - assert "application_name = '${APPLICATION_NAME}'" in source - assert "extract(epoch FROM backend_start)::text = '${backend_start_epoch}'" in source - assert '[[ "${termination_receipt}" == "1|true" ]]' in source def test_exit_cleanup_uses_the_same_guarded_backend_identity() -> None: From 58d6d680db6ef6f4b445e4c791d2d5d4377e882e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:19:49 +0900 Subject: [PATCH 40/68] test(document-records): forbid alternate recovery termination paths --- ..._idempotency_postcommit_recovery_contract.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/test_document_record_idempotency_postcommit_recovery_contract.py b/tests/test_document_record_idempotency_postcommit_recovery_contract.py index 548b4e6f0..487e183ac 100644 --- a/tests/test_document_record_idempotency_postcommit_recovery_contract.py +++ b/tests/test_document_record_idempotency_postcommit_recovery_contract.py @@ -1,11 +1,15 @@ """Regression contracts for post-commit document-record recovery acceptance.""" from pathlib import Path +import re COMPANION = Path(__file__).with_name( "document_record_idempotency_postcommit_recovery_companion.sh" ) +TERMINATE_BACKEND_CALL = re.compile( + r"\b(?:pg_catalog\.)?pg_terminate_backend\s*\(\s*([^()]+?)\s*\)" +) def _companion_source() -> str: @@ -18,6 +22,10 @@ def _shell_function(source: str, name: str, following_marker: str) -> str: return source[start:end] +def _termination_arguments(source: str) -> list[str]: + return [argument.strip() for argument in TERMINATE_BACKEND_CALL.findall(source)] + + def test_recovery_termination_binds_checked_backend_identity() -> None: """Termination must consume the same backend identity that observation accepted.""" @@ -34,21 +42,22 @@ def test_recovery_termination_binds_checked_backend_identity() -> None: "extract(epoch FROM backend_start)::text = '${backend_start_epoch}'" in termination ) - assert termination.count("pg_catalog.pg_terminate_backend(pid)") == 1 - assert "pg_terminate_backend(${backend_pid})" not in termination + assert _termination_arguments(source) == ["pid"] + assert _termination_arguments(termination) == ["pid"] + assert "pg_catalog.pg_terminate_backend" in termination assert '[[ "${termination_receipt}" == "1|true" ]]' in termination assert "extract(epoch FROM activity.backend_start)::text" in source def test_exit_cleanup_uses_the_same_guarded_backend_identity() -> None: - """Abort cleanup must not regress to PID-only backend termination.""" + """Abort cleanup must not introduce another backend-termination path.""" source = _companion_source() cleanup = _shell_function(source, "cleanup", "\n}\ntrap cleanup EXIT") assert "terminate_captured_backend" in cleanup - assert "pg_terminate_backend" not in cleanup + assert _termination_arguments(cleanup) == [] assert "backend_start_epoch" in cleanup assert "|| true" in cleanup From 36a806765663c88c08184acfad3a35a9900afa93 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:22:28 +0900 Subject: [PATCH 41/68] test(document-records): make backend termination lexical singleton --- ...idempotency_postcommit_recovery_contract.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/test_document_record_idempotency_postcommit_recovery_contract.py b/tests/test_document_record_idempotency_postcommit_recovery_contract.py index 487e183ac..46857975b 100644 --- a/tests/test_document_record_idempotency_postcommit_recovery_contract.py +++ b/tests/test_document_record_idempotency_postcommit_recovery_contract.py @@ -1,15 +1,11 @@ """Regression contracts for post-commit document-record recovery acceptance.""" from pathlib import Path -import re COMPANION = Path(__file__).with_name( "document_record_idempotency_postcommit_recovery_companion.sh" ) -TERMINATE_BACKEND_CALL = re.compile( - r"\b(?:pg_catalog\.)?pg_terminate_backend\s*\(\s*([^()]+?)\s*\)" -) def _companion_source() -> str: @@ -22,10 +18,6 @@ def _shell_function(source: str, name: str, following_marker: str) -> str: return source[start:end] -def _termination_arguments(source: str) -> list[str]: - return [argument.strip() for argument in TERMINATE_BACKEND_CALL.findall(source)] - - def test_recovery_termination_binds_checked_backend_identity() -> None: """Termination must consume the same backend identity that observation accepted.""" @@ -36,15 +28,17 @@ def test_recovery_termination_binds_checked_backend_identity() -> None: "\n}\n\ncleanup()", ) + # Fail closed on any second lexical occurrence, including an alternate call + # hidden behind PostgreSQL whitespace/comments or a separate helper. + assert source.count("pg_terminate_backend") == 1 + assert termination.count("pg_terminate_backend") == 1 + assert "pg_catalog.pg_terminate_backend(pid)" in termination assert "WHERE pid = ${backend_pid}" in termination assert "application_name = '${APPLICATION_NAME}'" in termination assert ( "extract(epoch FROM backend_start)::text = '${backend_start_epoch}'" in termination ) - assert _termination_arguments(source) == ["pid"] - assert _termination_arguments(termination) == ["pid"] - assert "pg_catalog.pg_terminate_backend" in termination assert '[[ "${termination_receipt}" == "1|true" ]]' in termination assert "extract(epoch FROM activity.backend_start)::text" in source @@ -57,7 +51,7 @@ def test_exit_cleanup_uses_the_same_guarded_backend_identity() -> None: cleanup = _shell_function(source, "cleanup", "\n}\ntrap cleanup EXIT") assert "terminate_captured_backend" in cleanup - assert _termination_arguments(cleanup) == [] + assert "pg_terminate_backend" not in cleanup assert "backend_start_epoch" in cleanup assert "|| true" in cleanup From 9534af759331006b3dea2314d87903be2a718a30 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:33:20 +0900 Subject: [PATCH 42/68] test(document-records): require deny-by-default persistence execute --- ...ecord_idempotency_function_acl_postgres.sh | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 tests/test_document_record_idempotency_function_acl_postgres.sh diff --git a/tests/test_document_record_idempotency_function_acl_postgres.sh b/tests/test_document_record_idempotency_function_acl_postgres.sh new file mode 100644 index 000000000..f13181a61 --- /dev/null +++ b/tests/test_document_record_idempotency_function_acl_postgres.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" + +for migration in \ + database/migrations/0001_foundation_schema.sql \ + database/migrations/0002_sealed_evidence_digest.sql \ + database/migrations/0021_document_record_persistence.sql \ + database/migrations/0022_document_record_evidence_unique_keys.sql \ + database/migrations/0023_document_record_canonical_encoding.sql \ + database/migrations/0024_document_record_idempotent_persistence.sql; do + if [[ ! -f "${migration}" ]]; then + echo "required document-record idempotency migration is missing: ${migration}" >&2 + exit 1 + fi + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f "${migration}" +done + +PROBE_ROLE_SUFFIX="$(python3 - <<'PY' +import uuid + +print(uuid.uuid4().hex[:24]) +PY +)" +PROBE_ROLE="orgmetra_document_persist_acl_probe_${PROBE_ROLE_SUFFIX}" +FUNCTION_SIGNATURE="public.persist_document_record_once(uuid,text,uuid,text,text,text,text,text,text,text,text,text,text,text,timestamptz,text,text,text,text,text)" + +cleanup_probe_role() { + local mode="${1:-strict}" + local role_exists + + if ! role_exists="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -c \ + "SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '${PROBE_ROLE}';" 2>/dev/null)"; then + if [[ "${mode}" == "best-effort" ]]; then + return 0 + fi + echo "could not verify temporary function-ACL probe-role cleanup" >&2 + return 1 + fi + if [[ "${role_exists}" != "1" ]]; then + return 0 + fi + if psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 >/dev/null 2>&1 <&2 + return 1 +} + +cleanup() { + cleanup_probe_role best-effort +} +trap cleanup EXIT + +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <&2 + exit 1 +fi + +set +e +probe_output="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 2>&1 <&2 + exit 1 +fi + +cleanup_probe_role strict +trap - EXIT From 18327beef7d1ca9a92479c8e8be8fe1ce16e3721 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:33:54 +0900 Subject: [PATCH 43/68] fix(document-records): deny public persistence execution --- .../0024_document_record_idempotent_persistence.sql | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/database/migrations/0024_document_record_idempotent_persistence.sql b/database/migrations/0024_document_record_idempotent_persistence.sql index 154d2c9dc..8a603db5a 100644 --- a/database/migrations/0024_document_record_idempotent_persistence.sql +++ b/database/migrations/0024_document_record_idempotent_persistence.sql @@ -322,6 +322,15 @@ BEGIN END; $$; +-- PostgreSQL grants EXECUTE on newly created functions to PUBLIC by default. +-- This persistence port is an application capability, not a cluster-wide API; +-- keep owner execution implicit and require any future service role to receive +-- an explicit purpose-bound grant in its owning provisioning boundary. +REVOKE EXECUTE ON FUNCTION public.persist_document_record_once( + uuid, text, uuid, text, text, text, text, text, text, text, text, text, + text, text, timestamptz, text, text, text, text, text +) FROM PUBLIC; + COMMENT ON FUNCTION public.persist_document_record_once( uuid, text, uuid, text, text, text, text, text, text, text, text, text, text, text, timestamptz, text, text, text, text, text From 4f61d3ddb2ce3afe062c8f8a5f83cf76a86f79ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:34:28 +0900 Subject: [PATCH 44/68] docs(document-records): bind persistence execute privilege --- ...309-document-record-idempotent-persistence.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/adr/0309-document-record-idempotent-persistence.md b/docs/adr/0309-document-record-idempotent-persistence.md index badcd122b..dc2f89427 100644 --- a/docs/adr/0309-document-record-idempotent-persistence.md +++ b/docs/adr/0309-document-record-idempotent-persistence.md @@ -18,6 +18,8 @@ The lock boundary must remain short. Document parsing, OCR, model inference, art The command accepts one opaque, purpose-bound idempotency key of the form `document-record-persist-`. It computes a server-side SHA-256 semantic digest over the complete governed persistence command, excluding only PostgreSQL-owned result time. The key itself is not part of the semantic digest; it identifies a retry family rather than changing document semantics. +PostgreSQL grants `EXECUTE` on newly created functions to `PUBLIC` by default. Migration 0024 therefore revokes `EXECUTE` on `persist_document_record_once(...)` from `PUBLIC` in the same transaction that creates the function. The migration owner retains its implicit owner capability; no generic database role becomes a document-persistence caller merely because it can connect to the database or use schema `public`. A future `document_records` service adapter must receive an explicit, purpose-bound `EXECUTE` grant through its own provisioning boundary instead of relying on PostgreSQL's ambient function default. + After null-authoritative-field validation, the function requires `current_tenant_record_id()` to equal `p_tenant_record_id`. A mismatch fails with SQLSTATE `42501` before semantic digest computation, replay lookup, advisory-lock acquisition, or any durable write. This explicit owner check remains required even though both document and receipt tables use FORCE RLS: a privileged migration/test connection can bypass RLS, and an advisory lock can otherwise be acquired for another tenant before row security is evaluated. Only after that tenant check does the function acquire `pg_advisory_xact_lock(hashtextextended(...))` over tenant + `document_records` namespace + key. The lock exists only until the current transaction ends. A same-key concurrent caller therefore waits until the first transaction commits or rolls back. No external I/O occurs while this lock is held. @@ -38,6 +40,8 @@ The implementation adds a tenant-qualified unique key to `document_record` so th **Retry heuristics in `talent_acquisition` or another consumer.** Rejected. Persistence replay truth belongs to `document_records`; copying mutable owner logic would create two authorities. +**Rely on PostgreSQL's default `PUBLIC` function EXECUTE privilege.** Rejected. `persist_document_record_once(...)` is a write capability for restricted HR metadata, not a cluster-wide utility. Authentication and tenant checks inside the function do not replace least-privilege admission to the function itself, and ambient defaults must not silently widen the callable persistence surface. + **Rely on table RLS to reject a mismatched tenant after lock acquisition.** Rejected. Row security protects table access, not database-global advisory-lock ownership. A mismatched request must be rejected before it can coordinate on another tenant's retry key, and privileged maintenance connections must not silently bypass the bounded-context tenant invariant. **Hold an explicit transaction open around upstream document processing.** Rejected. That would create the long-lived idle/lock behavior this architecture forbids. All expensive work must finish before entering `persist_document_record_once(...)`. @@ -48,10 +52,14 @@ The implementation adds a tenant-qualified unique key to `document_record` so th `tests/test_document_record_idempotency_postgres.sh` exercises real PostgreSQL sessions. It proves same-key/same-semantic retry convergence, same-key/different-semantic rejection, concurrent same-semantic convergence while the first transaction remains open, one durable document + one receipt, connection cleanup, receipt FORCE RLS, and append-only mutation rejection. The same command is also executed first under UTC and then under Asia/Seoul; digest identity must remain unchanged because the owner function canonicalizes its temporal serialization to UTC. All supported calls now provide the tenant session context explicitly instead of relying on a privileged test owner. +`tests/test_document_record_idempotency_function_acl_postgres.sh` creates a run-unique `NOLOGIN`/`NOBYPASSRLS` role with schema usage but no persistence capability. It requires `has_function_privilege(..., 'EXECUTE') = false` for that role while the migration owner still retains `EXECUTE`, then directly invokes the function under `SET ROLE` with deliberately invalid null arguments and requires PostgreSQL to reject the call at function authorization. Reaching command validation would prove that the ambient `PUBLIC` grant was still effective. The probe role is removed strictly on success and best-effort on an earlier assertion failure so a cleanup error cannot erase the primary failure. + `tests/test_document_record_idempotency_tenant_context_postgres.sh` supplies a valid tenant-beta command while the session tenant is tenant-alpha and requires the explicit owner error `document persistence tenant context does not match requested tenant`. It also proves that the rejected attempt leaves zero beta document and receipt rows. This contract intentionally remains valid even when the Foundation database owner can bypass RLS, because the owner function itself must enforce the tenant boundary before advisory-lock acquisition. PostgreSQL 16 documents `pg_advisory_xact_lock` as an exclusive transaction-level advisory lock that waits when necessary and is automatically released at transaction end. The function is explicitly `VOLATILE`; PostgreSQL's function-volatility contract gives volatile functions a fresh snapshot for each query they execute under the ordinary Read Committed transaction model. That fresh post-lock lookup is what lets a waiting retry observe the first transaction's committed receipt rather than reinterpret a uniqueness error as success. +PostgreSQL 16 also documents that newly created functions receive `EXECUTE` for `PUBLIC` by default and recommends revoking that privilege in the same transaction when a function is not intended for every database role. Migration 0024 follows that boundary explicitly rather than relying on cluster-specific `ALTER DEFAULT PRIVILEGES` state. + The owner function checks `transaction_isolation` before validating or mutating command state and fails closed unless it is `read committed`. `tests/test_document_record_idempotency_isolation_postgres.sh` enters a real `REPEATABLE READ` transaction and requires that isolation error before any command-field validation. Stronger isolation levels therefore cannot silently inherit semantics that depend on a fresh post-lock statement snapshot; a future successor must supply an explicit equivalent algorithm before relaxing this guard. The expired IETF HTTPAPI `Idempotency-Key` Internet-Draft is non-normative background only. Its key principles—one client-generated key for retries and no key reuse with a different payload—are compatible with this design, but the draft expired on 2026-04-18 and is not cited as an active standard. @@ -66,10 +74,12 @@ A caller that abandons a connection mid-transaction relies on PostgreSQL rollbac The explicit Read Committed guard intentionally rejects a caller that promotes this one operation to Repeatable Read or Serializable without a successor design. That is a compatibility boundary, not an invitation to weaken isolation elsewhere: the future adapter must scope transaction policy to this documented write contract. +Revoking `PUBLIC` execution means a future non-owner application principal will not work until provisioning deliberately grants that principal `EXECUTE` on this exact function signature. That is intentional fail-closed behavior. The service-role grant must be owned with the adapter/provisioning contract and tested as a bounded capability rather than added here without an authenticated service role. + ## Follow-up -- Admit `tests/test_document_record_idempotency_postgres.sh`, `tests/test_document_record_idempotency_isolation_postgres.sh`, and `tests/test_document_record_idempotency_tenant_context_postgres.sh` through the owner-neutral PostgreSQL Foundation registry once #310/#311 is reconciled with the document-record stack; do not add a feature-local workflow. -- Add the application/service adapter only after the `document_records` service boundary exists; it must map one external retry key and authenticated tenant context to this transaction without reimplementing replay logic. +- Admit `tests/test_document_record_idempotency_postgres.sh`, `tests/test_document_record_idempotency_function_acl_postgres.sh`, `tests/test_document_record_idempotency_isolation_postgres.sh`, and `tests/test_document_record_idempotency_tenant_context_postgres.sh` through the owner-neutral PostgreSQL Foundation registry once #310/#311 is reconciled with the document-record stack; do not add a feature-local workflow. +- Add the application/service adapter only after the `document_records` service boundary exists; it must map one external retry key and authenticated tenant context to this transaction without reimplementing replay logic, and its database principal must receive an explicit purpose-bound `EXECUTE` grant rather than inherit `PUBLIC` function access. - Re-run the full PostgreSQL acceptance on the exact protected-base head before changing this ADR from Proposed. - Keep #308 return/destruction completion receipts separate: persistence idempotency proves creation/retry identity, not later retention or destruction completion. @@ -80,3 +90,5 @@ Jena, J., & Dalal, S. (2025, October 15). *The Idempotency-Key HTTP Header Field PostgreSQL Global Development Group. (2026). *PostgreSQL 16 documentation: Advisory lock functions*. https://www.postgresql.org/docs/16/functions-admin.html PostgreSQL Global Development Group. (2026). *PostgreSQL 16 documentation: Function volatility categories*. https://www.postgresql.org/docs/16/xfunc-volatility.html + +PostgreSQL Global Development Group. (2026). *PostgreSQL 16 documentation: Privileges*. https://www.postgresql.org/docs/16/ddl-priv.html From 5fffa14c1b813b40da56215eef45a7d0fa279edc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:34:57 +0900 Subject: [PATCH 45/68] docs(document-records): trace persistence execute boundary --- .../document-record-idempotent-persistence.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md index 987e03c62..9df90e110 100644 --- a/docs/traceability/document-record-idempotent-persistence.md +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -5,6 +5,7 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | Requirement | Owner artifact | Executable evidence | Current state | | --- | --- | --- | --- | | One retry family has one tenant-scoped opaque key | `document_record_persist_receipt.idempotency_key` in migration 0024 | invalid/different-key behavior is constrained by the migration; #312 review remains pending | Implemented on Draft head | +| Persistence is not an ambient capability for every database role | migration 0024 revokes `EXECUTE` on `persist_document_record_once(...)` from `PUBLIC` in the same transaction that creates the function; the owner keeps its implicit capability and future service roles require an explicit purpose-bound grant | `tests/test_document_record_idempotency_function_acl_postgres.sh` creates a run-unique `NOLOGIN`/`NOBYPASSRLS` role with schema usage only, requires function EXECUTE=false for that role and true for the owner, then proves `SET ROLE` cannot enter the function body | Implemented; hosted execution pending Foundation admission | | Session tenant must match requested tenant before retry coordination | explicit `current_tenant_record_id() = p_tenant_record_id` guard before semantic digest/replay lock | `tests/test_document_record_idempotency_tenant_context_postgres.sh` submits a valid tenant-beta command from a tenant-alpha session, requires SQLSTATE-42501 boundary text, and proves zero beta document/receipt rows | Implemented; hosted execution pending Foundation admission | | Same key + same semantic command returns the first committed result | `persist_document_record_once(...)` semantic digest + replay branch | `tests/test_document_record_idempotency_postgres.sh` compares first and retry identity, audit/outbox references, semantic/receipt digests, and the original database-owned `recorded_at` instant; it then extracts the returned epoch and requires that exact value to equal both durable `document_record.recorded_at` and `document_record_persist_receipt.recorded_at` | Implemented; hosted execution pending Foundation admission | | Same key + changed semantics fails closed | server-side `orgmetra.document_record_persist_command.v1` SHA-256 | PostgreSQL contract changes only `application_evidence_digest_sha256` and requires the explicit semantic-conflict error | Implemented; hosted execution pending | @@ -21,7 +22,7 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | Lost-response retry can recover authoritative identity and time | receipt persists in the same transaction as the document write | `tests/document_record_idempotency_postcommit_recovery_companion.sh` runs after the main idempotency root has established migration state, submits one simple-query batch that commits the owner write and then blocks in `pg_sleep`; a supervisory connection waits until the receipt is externally visible, captures the exact backend `(pid, application_name, backend_start)` identity, terminates only that still-matching committed backend, requires the original client to fail, and retries the exact semantic command from a new connection. The retry must equal the durable document/receipt identity, digests, references, and original database-owned time with one document + one receipt only | Implemented executable companion; hosted execution pending Foundation admission | | Recovery termination cannot target a reused or foreign backend | per-execution UUID-derived `application_name` plus captured `pid` and `backend_start` | `tests/test_document_record_idempotency_postcommit_recovery_contract.py` requires the companion to capture `backend_start`, use an execution-unique session marker, and accept termination only when the same `(pid, application_name, backend_start)` still exists and `pg_terminate_backend` yields exact `1|true`; EXIT cleanup uses the same guarded helper | Implemented source contract; PostgreSQL execution pending Foundation admission | | Acceptance connections are closed | test sessions use execution-scoped `PGAPPNAME` values and are waited before inspection | both the concurrent root contract and post-commit recovery companion require `pg_stat_activity` to contain zero matching sessions after acceptance | Implemented; hosted execution pending | -| Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310/#311 must discover all three document-record idempotency PostgreSQL roots and execute the post-commit recovery script as an explicit reviewed companion of the main idempotency root; no feature-local workflow or fourth filename-specific root is added | Dependency pending stack reconciliation | +| Foundation cannot silently omit the new PostgreSQL contracts | #310/#311 owner-neutral discovery | #310/#311 must discover all four document-record idempotency PostgreSQL roots and execute the post-commit recovery script as an explicit reviewed companion of the main idempotency root; no feature-local workflow or filename-specific switch is added | Dependency pending stack reconciliation | | Creation/retry receipt is not destruction-completion evidence | ADR 0309 / #308 boundary | #307 dependency order keeps #309/#312 and #308 as distinct prerequisites | Explicitly separated | ## Evidence lineage @@ -51,10 +52,13 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel - Companion execution-order repair: `cb587679768eeb62712ca529f30d7a630817d5bd`; the companion no longer reapplies migrations and instead fails closed unless the main idempotency root has already established the owner schema/function, matching Foundation root→companion semantics. - Backend-identity RED contract: `626c62e53dfb9a4a92ad711d19699c6cb922a2ba`; the prior companion terminated by captured PID alone and used one fixed application marker, so checked backend identity was not bound through use. - Backend-identity causal repair: `8a485d60514e1a69124294c6656ffae0bb5d4f98`; recovery sessions now use UUID-derived markers, capture `backend_start`, and terminate/cleanup only an exact still-live `(pid, application_name, backend_start)` match with a `1|true` receipt. +- Persistence-function ACL RED contract: `9534af759331006b3dea2314d87903be2a718a30`; under PostgreSQL's documented defaults a fresh function is executable through `PUBLIC`, so a generic probe role would still have the capability. +- Persistence-function ACL causal fix: `18327beef7d1ca9a92479c8e8be8fe1ce16e3721`; migration 0024 revokes `EXECUTE` from `PUBLIC` inside the same transaction that creates `persist_document_record_once(...)`. +- ACL decision/evidence currentization: `4f61d3ddb2ce3afe062c8f8a5f83cf76a86f79ba`; ADR 0309 records the explicit future service-role grant boundary and PostgreSQL privilege authority. ## Evidence limits -No hosted PostgreSQL execution is claimed on the current stacked branch. #312 targets #107, while the canonical PostgreSQL Foundation implementation is separately stacked under #259/#311. Exact-head GREEN requires ordinary-forward reconciliation of those histories and a fresh run that discovers all three roots plus the reviewed post-commit recovery companion without filename-specific workflow logic. +No hosted PostgreSQL execution is claimed on the current stacked branch. #312 targets #107, while the canonical PostgreSQL Foundation implementation is separately stacked under #259/#311. Exact-head GREEN requires ordinary-forward reconciliation of those histories and a fresh run that discovers all four roots plus the reviewed post-commit recovery companion without filename-specific workflow logic. The bounded observation loops in the concurrency and post-commit-recovery acceptance only wait for explicit PostgreSQL state. They do not use elapsed time as evidence that serialization or commit happened: concurrency fails unless the second backend is actually shown waiting on the first backend's advisory lock; post-commit recovery fails unless another session can read the durable receipt while the original backend is still alive in `PgSleep` before that exact captured backend identity is terminated. From 874a3eb2f26af094a604c8fe9b07b8a946b54e8f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:35:19 +0900 Subject: [PATCH 46/68] test(document-records): fix ACL owner privilege probe --- tests/test_document_record_idempotency_function_acl_postgres.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_document_record_idempotency_function_acl_postgres.sh b/tests/test_document_record_idempotency_function_acl_postgres.sh index f13181a61..8435c5f08 100644 --- a/tests/test_document_record_idempotency_function_acl_postgres.sh +++ b/tests/test_document_record_idempotency_function_acl_postgres.sh @@ -74,7 +74,7 @@ SELECT 'EXECUTE' )::text || '|' || pg_catalog.has_function_privilege( - pg_catalog.current_user, + current_user, '${FUNCTION_SIGNATURE}', 'EXECUTE' )::text; From 94078c680cf363fe5fc88e3f75ed34ca38802c25 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:43:47 +0900 Subject: [PATCH 47/68] fix(foundation): register idempotent document-record artifacts --- scripts/foundation-contract-core.mjs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/foundation-contract-core.mjs b/scripts/foundation-contract-core.mjs index ac190292d..a9b56d8de 100644 --- a/scripts/foundation-contract-core.mjs +++ b/scripts/foundation-contract-core.mjs @@ -52,9 +52,11 @@ export const REQUIRED_FILES = Object.freeze([ 'docs/adr/0013-governed-requisition-review-packet.md', 'docs/adr/0014-job-analysis-snapshot-persistence.md', 'docs/adr/0107-document-record-persistence.md', + 'docs/adr/0309-document-record-idempotent-persistence.md', 'docs/doctoring/REFERENCES.md', 'docs/doctoring/document-record-persistence-references.md', 'docs/traceability/document-record-persistence.md', + 'docs/traceability/document-record-idempotent-persistence.md', 'docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md', 'docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md', 'database/migrations/0001_foundation_schema.sql', @@ -73,6 +75,7 @@ export const REQUIRED_FILES = Object.freeze([ 'database/migrations/0021_document_record_persistence.sql', 'database/migrations/0022_document_record_evidence_unique_keys.sql', 'database/migrations/0023_document_record_canonical_encoding.sql', + 'database/migrations/0024_document_record_idempotent_persistence.sql', 'packages/hris-kernel/src/orgmetra_hris_kernel/audit.py', 'packages/hris-kernel/tests/test_audit_outbox.py', 'schemas/openapi.yaml', @@ -97,6 +100,12 @@ export const REQUIRED_FILES = Object.freeze([ 'tests/test_document_record_canonical_bytes_postgres.sh', 'tests/test_document_record_evidence_unique_keys_postgres.sh', 'tests/test_document_record_persistence_postgres.sh', + 'tests/test_document_record_idempotency_postgres.sh', + 'tests/test_document_record_idempotency_function_acl_postgres.sh', + 'tests/test_document_record_idempotency_isolation_postgres.sh', + 'tests/test_document_record_idempotency_tenant_context_postgres.sh', + 'tests/document_record_idempotency_postcommit_recovery_companion.sh', + 'tests/test_document_record_idempotency_postcommit_recovery_contract.py', 'tests/validate_repository.py' ]); @@ -694,4 +703,4 @@ export function runCli(rootPath, outputStream = process.stdout, errorStream = pr } errorStream.write(`${JSON.stringify({ status: 'failed', error_count: errors.length, errors }, null, 2)}\n`); return 1; -} +} \ No newline at end of file From 93d75e8ca48a62998d6b156d78c54c67ff765227 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:45:08 +0900 Subject: [PATCH 48/68] fix(foundation): mirror idempotency provenance inventory --- tests/validate_repository.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/validate_repository.py b/tests/validate_repository.py index 498974a01..d97c1ec6e 100644 --- a/tests/validate_repository.py +++ b/tests/validate_repository.py @@ -55,9 +55,11 @@ "docs/adr/0013-governed-requisition-review-packet.md", "docs/adr/0014-job-analysis-snapshot-persistence.md", "docs/adr/0107-document-record-persistence.md", + "docs/adr/0309-document-record-idempotent-persistence.md", "docs/doctoring/REFERENCES.md", "docs/doctoring/document-record-persistence-references.md", "docs/traceability/document-record-persistence.md", + "docs/traceability/document-record-idempotent-persistence.md", "docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md", "docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md", "database/migrations/0001_foundation_schema.sql", @@ -76,6 +78,7 @@ "database/migrations/0021_document_record_persistence.sql", "database/migrations/0022_document_record_evidence_unique_keys.sql", "database/migrations/0023_document_record_canonical_encoding.sql", + "database/migrations/0024_document_record_idempotent_persistence.sql", "packages/hris-kernel/src/orgmetra_hris_kernel/audit.py", "packages/hris-kernel/tests/test_audit_outbox.py", "schemas/openapi.yaml", @@ -100,6 +103,12 @@ "tests/test_document_record_canonical_bytes_postgres.sh", "tests/test_document_record_evidence_unique_keys_postgres.sh", "tests/test_document_record_persistence_postgres.sh", + "tests/test_document_record_idempotency_postgres.sh", + "tests/test_document_record_idempotency_function_acl_postgres.sh", + "tests/test_document_record_idempotency_isolation_postgres.sh", + "tests/test_document_record_idempotency_tenant_context_postgres.sh", + "tests/document_record_idempotency_postcommit_recovery_companion.sh", + "tests/test_document_record_idempotency_postcommit_recovery_contract.py", "tests/validate_repository.py", ] @@ -643,4 +652,4 @@ def main() -> None: if __name__ == "__main__": - main() + main() \ No newline at end of file From 3e7866878a628cf6e2503357d8253c984baa721d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 18:49:53 +0900 Subject: [PATCH 49/68] fix(foundation): restore validator terminal newline --- tests/validate_repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/validate_repository.py b/tests/validate_repository.py index d97c1ec6e..8a83b2485 100644 --- a/tests/validate_repository.py +++ b/tests/validate_repository.py @@ -652,4 +652,4 @@ def main() -> None: if __name__ == "__main__": - main() \ No newline at end of file + main() From 8db666f33804058b377b393406b73675bbb01a2e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 19:16:12 +0900 Subject: [PATCH 50/68] fix(document-records): reseal idempotency provenance manifest --- manifest.json | 78 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/manifest.json b/manifest.json index 9a86e5412..a3e681974 100644 --- a/manifest.json +++ b/manifest.json @@ -153,6 +153,12 @@ "bytes": 4364, "lines": 87 }, + { + "path": "database/migrations/0024_document_record_idempotent_persistence.sql", + "sha256": "502637f43b166f16be355a3f159f10151c61dfdd9b2849d4a5d38cc73404589f", + "bytes": 14343, + "lines": 340 + }, { "path": "docs/API_CONTRACT.md", "sha256": "63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589", @@ -173,9 +179,9 @@ }, { "path": "docs/OPERABILITY.md", - "sha256": "82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62", - "bytes": 11189, - "lines": 71 + "sha256": "591c765b107f84e76b3f380adfafc3fec67c0735e6d3f33431369659f4f28c15", + "bytes": 14597, + "lines": 85 }, { "path": "docs/PRD.md", @@ -203,9 +209,9 @@ }, { "path": "docs/TEST_STRATEGY.md", - "sha256": "d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8", - "bytes": 16534, - "lines": 135 + "sha256": "48596f3258300feedaa42bedc89ac59ef8775a321d323243c1d29ef52e5db37e", + "bytes": 19089, + "lines": 139 }, { "path": "docs/THREAT_MODEL.md", @@ -333,6 +339,12 @@ "bytes": 6448, "lines": 48 }, + { + "path": "docs/adr/0309-document-record-idempotent-persistence.md", + "sha256": "910b544a3be0f9c6f41893e1e78f7362ff53e36b053f26112a96a4e7bcb2ad11", + "bytes": 13534, + "lines": 94 + }, { "path": "docs/adr/README.md", "sha256": "f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002", @@ -369,6 +381,12 @@ "bytes": 4120, "lines": 30 }, + { + "path": "docs/traceability/document-record-idempotent-persistence.md", + "sha256": "310068058910f921109c295730e0fd41fa3cccde4d403b8610cbed0a82d9947e", + "bytes": 12809, + "lines": 65 + }, { "path": "package.json", "sha256": "59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5", @@ -395,9 +413,9 @@ }, { "path": "scripts/foundation-contract-core.mjs", - "sha256": "2305e1a6efff8b0dc47b40d517ac49b2a86f780899ae03c83d4063ed30d169e5", - "bytes": 28670, - "lines": 697 + "sha256": "aa1965c7101551570a64c9e679c2f9042eb449173fa82face5e94e9c72ded8d1", + "bytes": 29278, + "lines": 705 }, { "path": "scripts/foundation-contract.mjs", @@ -405,6 +423,12 @@ "bytes": 218, "lines": 6 }, + { + "path": "tests/document_record_idempotency_postcommit_recovery_companion.sh", + "sha256": "ecbde2131fc5341dc11017305d0bb114136dd1289a05ece14e7c3080a8a52e93", + "bytes": 11193, + "lines": 277 + }, { "path": "tests/dispatcher-inventory.test.mjs", "sha256": "09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261", @@ -465,6 +489,36 @@ "bytes": 5058, "lines": 103 }, + { + "path": "tests/test_document_record_idempotency_function_acl_postgres.sh", + "sha256": "ba2f60295d1cbed7eb773ece4a79e11b6b186df4301a440d0c96e407663e3999", + "bytes": 3500, + "lines": 123 + }, + { + "path": "tests/test_document_record_idempotency_isolation_postgres.sh", + "sha256": "a8e7a099e9b046753591167ddfe4e4206bccd2f00f182925720fa3c15b880a32", + "bytes": 1873, + "lines": 62 + }, + { + "path": "tests/test_document_record_idempotency_postcommit_recovery_contract.py", + "sha256": "69cea86ef064c7bc8135d1e28f8dfd6eb893fdd6ee8b314045492971adc95aa1", + "bytes": 2318, + "lines": 68 + }, + { + "path": "tests/test_document_record_idempotency_postgres.sh", + "sha256": "db078f2d5876e844d455915d3446dc8c25ca8ba95cac87481c26911f839e246d", + "bytes": 17324, + "lines": 413 + }, + { + "path": "tests/test_document_record_idempotency_tenant_context_postgres.sh", + "sha256": "171f35d16891ac58d67a54504ce051a4bff0fedbf7ab43bed2f9ef3f3765b945", + "bytes": 5830, + "lines": 140 + }, { "path": "tests/test_document_record_persistence_postgres.sh", "sha256": "f3d5fcb83a406ac202986f0272f673a8f3c6349752119d27b4d42e4c6bbe7072", @@ -521,9 +575,9 @@ }, { "path": "tests/validate_repository.py", - "sha256": "0692bfa17ebed3ba6594bc30e75bfeb8e93784ae381af3801457f4fe6cf41529", - "bytes": 27804, - "lines": 646 + "sha256": "6c39b7e25ed127b34a74532ae5e607943b1f73b964bc931f256dc99df75f6b54", + "bytes": 28431, + "lines": 655 } ] } From d79580d24a424b66b2e3ec60ce0c8870c0af45dc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 19:35:34 +0900 Subject: [PATCH 51/68] fix(document-records): align exact provenance seal --- manifest.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/manifest.json b/manifest.json index a3e681974..b571a7eec 100644 --- a/manifest.json +++ b/manifest.json @@ -29,9 +29,9 @@ }, { "path": "CHANGELOG.md", - "sha256": "f2d2e0b488c0440533effa821808f2f17e37d92f8fb586174c2fdb594f760ca5", - "bytes": 17539, - "lines": 77 + "sha256": "321c43f388dd561b8867684daac74b0c21f3676d66e15f941feed28d5cf02459", + "bytes": 17829, + "lines": 78 }, { "path": "CLAUDE.md", @@ -375,18 +375,18 @@ "bytes": 6237, "lines": 187 }, - { - "path": "docs/traceability/document-record-persistence.md", - "sha256": "3849ab6642f8b171aae6b379781fb19028635996428fbc37788334bd1feb167a", - "bytes": 4120, - "lines": 30 - }, { "path": "docs/traceability/document-record-idempotent-persistence.md", "sha256": "310068058910f921109c295730e0fd41fa3cccde4d403b8610cbed0a82d9947e", "bytes": 12809, "lines": 65 }, + { + "path": "docs/traceability/document-record-persistence.md", + "sha256": "3849ab6642f8b171aae6b379781fb19028635996428fbc37788334bd1feb167a", + "bytes": 4120, + "lines": 30 + }, { "path": "package.json", "sha256": "59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5", @@ -415,7 +415,7 @@ "path": "scripts/foundation-contract-core.mjs", "sha256": "aa1965c7101551570a64c9e679c2f9042eb449173fa82face5e94e9c72ded8d1", "bytes": 29278, - "lines": 705 + "lines": 706 }, { "path": "scripts/foundation-contract.mjs", @@ -423,18 +423,18 @@ "bytes": 218, "lines": 6 }, - { - "path": "tests/document_record_idempotency_postcommit_recovery_companion.sh", - "sha256": "ecbde2131fc5341dc11017305d0bb114136dd1289a05ece14e7c3080a8a52e93", - "bytes": 11193, - "lines": 277 - }, { "path": "tests/dispatcher-inventory.test.mjs", "sha256": "09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261", "bytes": 1597, "lines": 34 }, + { + "path": "tests/document_record_idempotency_postcommit_recovery_companion.sh", + "sha256": "ecbde2131fc5341dc11017305d0bb114136dd1289a05ece14e7c3080a8a52e93", + "bytes": 11193, + "lines": 277 + }, { "path": "tests/foundation-contract.test.mjs", "sha256": "648533b4aff8cee643df4afc06b463eda788e002e11d043971c8a16804c68501", @@ -580,4 +580,4 @@ "lines": 655 } ] -} +} \ No newline at end of file From 5fa9b191864b11f6a842c64a84e03e5a532525de Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 19:36:32 +0900 Subject: [PATCH 52/68] style(document-records): normalize manifest terminator --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index b571a7eec..d9e940dd1 100644 --- a/manifest.json +++ b/manifest.json @@ -580,4 +580,4 @@ "lines": 655 } ] -} \ No newline at end of file +} From 12b474b5fd4218d6d9d94e5c2869b9c503035b71 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 20:35:09 +0900 Subject: [PATCH 53/68] test(document-records): require purpose-bound persistence capability roles --- ...ecord_idempotency_function_acl_postgres.sh | 152 ++++++++++++++++-- 1 file changed, 138 insertions(+), 14 deletions(-) diff --git a/tests/test_document_record_idempotency_function_acl_postgres.sh b/tests/test_document_record_idempotency_function_acl_postgres.sh index 8435c5f08..c974a5218 100644 --- a/tests/test_document_record_idempotency_function_acl_postgres.sh +++ b/tests/test_document_record_idempotency_function_acl_postgres.sh @@ -17,6 +17,8 @@ for migration in \ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f "${migration}" done +FUNCTION_OWNER_ROLE="orgmetra_document_persistence_owner" +FUNCTION_EXECUTOR_ROLE="orgmetra_document_persistence_executor" PROBE_ROLE_SUFFIX="$(python3 - <<'PY' import uuid @@ -60,27 +62,149 @@ cleanup() { } trap cleanup EXIT +capability_role_state="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -c " +SELECT + owner_role.rolcanlogin::text || '|' || + owner_role.rolsuper::text || '|' || + owner_role.rolcreatedb::text || '|' || + owner_role.rolcreaterole::text || '|' || + owner_role.rolreplication::text || '|' || + owner_role.rolbypassrls::text || '|' || + executor_role.rolcanlogin::text || '|' || + executor_role.rolsuper::text || '|' || + executor_role.rolcreatedb::text || '|' || + executor_role.rolcreaterole::text || '|' || + executor_role.rolreplication::text || '|' || + executor_role.rolbypassrls::text +FROM pg_catalog.pg_roles AS owner_role +JOIN pg_catalog.pg_roles AS executor_role + ON executor_role.rolname = '${FUNCTION_EXECUTOR_ROLE}' +WHERE owner_role.rolname = '${FUNCTION_OWNER_ROLE}'; +")" +if [[ "${capability_role_state}" != "false|false|false|false|false|false|false|false|false|false|false|false" ]]; then + echo "document persistence capability roles are missing or not deny-default NOLOGIN/NOBYPASSRLS roles: ${capability_role_state}" >&2 + exit 1 +fi + +function_authority_state="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -c " +SELECT + function_owner.rolname || '|' || + function_record.prosecdef::text || '|' || + pg_catalog.has_schema_privilege('${FUNCTION_OWNER_ROLE}', 'public', 'USAGE')::text || '|' || + pg_catalog.has_schema_privilege('${FUNCTION_OWNER_ROLE}', 'public', 'CREATE')::text || '|' || + pg_catalog.has_schema_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public', 'USAGE')::text || '|' || + pg_catalog.has_schema_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public', 'CREATE')::text || '|' || + pg_catalog.has_function_privilege('${FUNCTION_OWNER_ROLE}', '${FUNCTION_SIGNATURE}', 'EXECUTE')::text || '|' || + pg_catalog.has_function_privilege('${FUNCTION_EXECUTOR_ROLE}', '${FUNCTION_SIGNATURE}', 'EXECUTE')::text +FROM pg_catalog.pg_proc AS function_record +JOIN pg_catalog.pg_roles AS function_owner + ON function_owner.oid = function_record.proowner +WHERE function_record.oid = '${FUNCTION_SIGNATURE}'::pg_catalog.regprocedure; +")" +if [[ "${function_authority_state}" != "${FUNCTION_OWNER_ROLE}|true|true|false|true|false|true|true" ]]; then + echo "document persistence function is not a purpose-bound SECURITY DEFINER capability: ${function_authority_state}" >&2 + exit 1 +fi + +owner_table_state="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -c " +SELECT + pg_catalog.has_table_privilege('${FUNCTION_OWNER_ROLE}', 'public.document_record', 'SELECT')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_OWNER_ROLE}', 'public.document_record', 'INSERT')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_OWNER_ROLE}', 'public.document_record', 'UPDATE')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_OWNER_ROLE}', 'public.document_record', 'DELETE')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_OWNER_ROLE}', 'public.document_record', 'TRUNCATE')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_OWNER_ROLE}', 'public.document_record_persist_receipt', 'SELECT')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_OWNER_ROLE}', 'public.document_record_persist_receipt', 'INSERT')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_OWNER_ROLE}', 'public.document_record_persist_receipt', 'UPDATE')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_OWNER_ROLE}', 'public.document_record_persist_receipt', 'DELETE')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_OWNER_ROLE}', 'public.document_record_persist_receipt', 'TRUNCATE')::text; +")" +if [[ "${owner_table_state}" != "true|true|false|false|false|true|true|false|false|false" ]]; then + echo "document persistence function owner has an unexpected direct-table privilege set: ${owner_table_state}" >&2 + exit 1 +fi + +executor_table_state="$(psql "${DATABASE_URL}" -Atq -v ON_ERROR_STOP=1 -c " +SELECT + pg_catalog.has_table_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public.document_record', 'SELECT')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public.document_record', 'INSERT')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public.document_record', 'UPDATE')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public.document_record', 'DELETE')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public.document_record', 'TRUNCATE')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public.document_record_persist_receipt', 'SELECT')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public.document_record_persist_receipt', 'INSERT')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public.document_record_persist_receipt', 'UPDATE')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public.document_record_persist_receipt', 'DELETE')::text || '|' || + pg_catalog.has_table_privilege('${FUNCTION_EXECUTOR_ROLE}', 'public.document_record_persist_receipt', 'TRUNCATE')::text; +")" +if [[ "${executor_table_state}" != "false|false|false|false|false|false|false|false|false|false" ]]; then + echo "document persistence executor can bypass the function through direct table privileges: ${executor_table_state}" >&2 + exit 1 +fi + +set +e +executor_direct_table_output="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 2>&1 <&2 + exit 1 +fi + +set +e +executor_function_output="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 2>&1 <&2 + exit 1 +fi + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <&2 +if [[ "${probe_acl_state}" != "false" ]]; then + echo "unprivileged probe role unexpectedly inherited document persistence EXECUTE: ${probe_acl_state}" >&2 exit 1 fi From 50c0259a24089cba7e848ab97887cbf0f581746e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 20:35:52 +0900 Subject: [PATCH 54/68] fix(document-records): fence persistence behind execute-only capability --- ...document_record_idempotent_persistence.sql | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/database/migrations/0024_document_record_idempotent_persistence.sql b/database/migrations/0024_document_record_idempotent_persistence.sql index 8a603db5a..cc8be50a4 100644 --- a/database/migrations/0024_document_record_idempotent_persistence.sql +++ b/database/migrations/0024_document_record_idempotent_persistence.sql @@ -3,6 +3,26 @@ -- database transaction that checks replay state and writes the immutable fact; -- no external computation or network work occurs while it is held. +-- Capability-role names are security boundaries. Reusing an existing cluster +-- role could retain memberships or object ACLs that CREATE ROLE cannot erase. +-- Fail before changing any project object so a collision cannot leave partial +-- persistence migration state behind. +DO $orgmetra_document_persistence_role_preflight$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_catalog.pg_roles + WHERE rolname IN ( + 'orgmetra_document_persistence_owner', + 'orgmetra_document_persistence_executor' + ) + ) THEN + RAISE EXCEPTION 'pre-existing document persistence capability role is not accepted' + USING ERRCODE = '42710'; + END IF; +END; +$orgmetra_document_persistence_role_preflight$; + BEGIN; SET LOCAL search_path = public, pg_catalog; @@ -323,18 +343,52 @@ END; $$; -- PostgreSQL grants EXECUTE on newly created functions to PUBLIC by default. --- This persistence port is an application capability, not a cluster-wide API; --- keep owner execution implicit and require any future service role to receive --- an explicit purpose-bound grant in its owning provisioning boundary. +-- Revoke that ambient capability before transferring this write boundary to a +-- dedicated NOLOGIN owner. The externally assignable executor gets only +-- schema USAGE + function EXECUTE and therefore cannot bypass replay semantics +-- through direct table DML. REVOKE EXECUTE ON FUNCTION public.persist_document_record_once( uuid, text, uuid, text, text, text, text, text, text, text, text, text, text, text, timestamptz, text, text, text, text, text ) FROM PUBLIC; +CREATE ROLE orgmetra_document_persistence_owner + NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS; +CREATE ROLE orgmetra_document_persistence_executor + NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS; + +GRANT USAGE ON SCHEMA public + TO orgmetra_document_persistence_owner, orgmetra_document_persistence_executor; +GRANT SELECT, INSERT ON TABLE public.document_record + TO orgmetra_document_persistence_owner; +GRANT SELECT, INSERT ON TABLE public.document_record_persist_receipt + TO orgmetra_document_persistence_owner; +GRANT EXECUTE ON FUNCTION public.current_tenant_record_id() + TO orgmetra_document_persistence_owner; +GRANT EXECUTE ON FUNCTION public.digest(bytea, text) + TO orgmetra_document_persistence_owner; + +-- ALTER FUNCTION OWNER requires CREATE on the containing schema for the target +-- owner. Grant it only for this ownership handoff, then revoke it before commit. +GRANT CREATE ON SCHEMA public TO orgmetra_document_persistence_owner; +ALTER FUNCTION public.persist_document_record_once( + uuid, text, uuid, text, text, text, text, text, text, text, text, text, + text, text, timestamptz, text, text, text, text, text +) OWNER TO orgmetra_document_persistence_owner; +ALTER FUNCTION public.persist_document_record_once( + uuid, text, uuid, text, text, text, text, text, text, text, text, text, + text, text, timestamptz, text, text, text, text, text +) SECURITY DEFINER; +REVOKE CREATE ON SCHEMA public FROM orgmetra_document_persistence_owner; +GRANT EXECUTE ON FUNCTION public.persist_document_record_once( + uuid, text, uuid, text, text, text, text, text, text, text, text, text, + text, text, timestamptz, text, text, text, text, text +) TO orgmetra_document_persistence_executor; + COMMENT ON FUNCTION public.persist_document_record_once( uuid, text, uuid, text, text, text, text, text, text, text, text, text, text, text, timestamptz, text, text, text, text, text ) IS - 'Persists one immutable document-record fact and replay receipt under a tenant-scoped transaction advisory lock. The caller tenant context must match the requested tenant before any replay lock or durable write. The owner fails closed outside Read Committed because replay visibility relies on a fresh post-lock statement snapshot. Same-key same-semantic retries return the first committed result; changed semantics fail closed. Digest serialization uses function-local UTC so equivalent timestamptz values do not change replay identity across caller sessions.'; + 'Persists one immutable document-record fact and replay receipt under a tenant-scoped transaction advisory lock. The SECURITY DEFINER function is owned by a dedicated NOLOGIN/NOBYPASSRLS role with only SELECT/INSERT on document persistence tables; the externally assignable executor role has EXECUTE only and cannot bypass replay semantics with direct DML. The caller tenant context must match the requested tenant before any replay lock or durable write. The owner fails closed outside Read Committed because replay visibility relies on a fresh post-lock statement snapshot. Same-key same-semantic retries return the first committed result; changed semantics fail closed. Digest serialization uses function-local UTC so equivalent timestamptz values do not change replay identity across caller sessions.'; COMMIT; From 682e254b41e84aec824af8409205e47630095aea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 20:37:57 +0900 Subject: [PATCH 55/68] docs(document-records): record execute-only persistence capability --- ...-document-record-idempotent-persistence.md | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/adr/0309-document-record-idempotent-persistence.md b/docs/adr/0309-document-record-idempotent-persistence.md index dc2f89427..0417c4469 100644 --- a/docs/adr/0309-document-record-idempotent-persistence.md +++ b/docs/adr/0309-document-record-idempotent-persistence.md @@ -12,13 +12,17 @@ A caller can lose the response after PostgreSQL commits. If it retries the same The lock boundary must remain short. Document parsing, OCR, model inference, artifact transfer, or any other network/compute work is not permitted inside the database transaction used for retry arbitration. Tenant identity must also be verified before the function acquires any advisory lock; RLS at the eventual table write is too late because advisory locks are database-global coordination state rather than row-scoped state. +The callable database boundary must also preserve the same invariant. A `SECURITY INVOKER` function cannot be treated as an execute-only persistence capability if a future service principal also needs direct `SELECT`/`INSERT` on the underlying tables to make the function work: those privileges would let that principal bypass the replay receipt and call path entirely. + ## Decision `document_records` owns a tenant-scoped `document_record_persist_receipt` and the `persist_document_record_once(...)` transaction boundary. The command accepts one opaque, purpose-bound idempotency key of the form `document-record-persist-`. It computes a server-side SHA-256 semantic digest over the complete governed persistence command, excluding only PostgreSQL-owned result time. The key itself is not part of the semantic digest; it identifies a retry family rather than changing document semantics. -PostgreSQL grants `EXECUTE` on newly created functions to `PUBLIC` by default. Migration 0024 therefore revokes `EXECUTE` on `persist_document_record_once(...)` from `PUBLIC` in the same transaction that creates the function. The migration owner retains its implicit owner capability; no generic database role becomes a document-persistence caller merely because it can connect to the database or use schema `public`. A future `document_records` service adapter must receive an explicit, purpose-bound `EXECUTE` grant through its own provisioning boundary instead of relying on PostgreSQL's ambient function default. +PostgreSQL grants `EXECUTE` on newly created functions to `PUBLIC` by default. Migration 0024 revokes that ambient grant and establishes two reserved NOLOGIN/NOBYPASSRLS roles after a fail-before-mutation collision preflight. `orgmetra_document_persistence_owner` owns `persist_document_record_once(...)` as a `SECURITY DEFINER` function and receives only schema usage plus the `SELECT`/`INSERT` and helper-function privileges required to implement the transaction. `orgmetra_document_persistence_executor` receives schema usage and `EXECUTE` on that exact function signature, but no direct document or receipt table privilege. The owner receives schema `CREATE` only long enough to complete the ownership handoff and loses it before the migration commits. Production login identities may acquire the executor capability only through explicit purpose-bound membership provisioning; migration execution requires authority to create the two fresh service-owned roles. + +This is a narrow capability boundary, not a general preference for `SECURITY DEFINER`. The function pins `search_path`, the definer is NOLOGIN/NOBYPASSRLS and is not the table owner, FORCE RLS remains active, and direct DML remains unavailable to the externally assignable executor. The design follows the repository's existing hardened operator-recovery pattern rather than granting the application role both function execution and bypass-capable table DML. After null-authoritative-field validation, the function requires `current_tenant_record_id()` to equal `p_tenant_record_id`. A mismatch fails with SQLSTATE `42501` before semantic digest computation, replay lookup, advisory-lock acquisition, or any durable write. This explicit owner check remains required even though both document and receipt tables use FORCE RLS: a privileged migration/test connection can bypass RLS, and an advisory lock can otherwise be acquired for another tenant before row security is evaluated. @@ -42,6 +46,10 @@ The implementation adds a tenant-qualified unique key to `document_record` so th **Rely on PostgreSQL's default `PUBLIC` function EXECUTE privilege.** Rejected. `persist_document_record_once(...)` is a write capability for restricted HR metadata, not a cluster-wide utility. Authentication and tenant checks inside the function do not replace least-privilege admission to the function itself, and ambient defaults must not silently widen the callable persistence surface. +**Keep the function `SECURITY INVOKER` and grant a service principal the table privileges it needs.** Rejected. The service principal would then be able to insert or read persistence tables directly and bypass the exact idempotency/replay boundary that the function is intended to own. Execute-only admission requires a separate hardened function owner with the table privileges and a caller role with no direct DML. + +**Make the migration or login role the `SECURITY DEFINER` owner.** Rejected. A login or cluster-powerful migration role would make function compromise materially broader. The dedicated owner is NOLOGIN, NOBYPASSRLS, denied schema CREATE after handoff, and receives only the object privileges required by this one capability. + **Rely on table RLS to reject a mismatched tenant after lock acquisition.** Rejected. Row security protects table access, not database-global advisory-lock ownership. A mismatched request must be rejected before it can coordinate on another tenant's retry key, and privileged maintenance connections must not silently bypass the bounded-context tenant invariant. **Hold an explicit transaction open around upstream document processing.** Rejected. That would create the long-lived idle/lock behavior this architecture forbids. All expensive work must finish before entering `persist_document_record_once(...)`. @@ -50,15 +58,15 @@ The implementation adds a tenant-qualified unique key to `document_record` so th ## Evidence and acceptance -`tests/test_document_record_idempotency_postgres.sh` exercises real PostgreSQL sessions. It proves same-key/same-semantic retry convergence, same-key/different-semantic rejection, concurrent same-semantic convergence while the first transaction remains open, one durable document + one receipt, connection cleanup, receipt FORCE RLS, and append-only mutation rejection. The same command is also executed first under UTC and then under Asia/Seoul; digest identity must remain unchanged because the owner function canonicalizes its temporal serialization to UTC. All supported calls now provide the tenant session context explicitly instead of relying on a privileged test owner. +`tests/test_document_record_idempotency_postgres.sh` exercises real PostgreSQL sessions. It proves same-key/same-semantic retry convergence, same-key/different-semantic rejection, concurrent same-semantic convergence while the first transaction remains open, one durable document + one receipt, connection cleanup, receipt FORCE RLS, and append-only mutation rejection. The same command is also executed first under UTC and then under Asia/Seoul; digest identity must remain unchanged because the owner function canonicalizes its temporal serialization to UTC. All supported calls now provide the tenant session context explicitly instead of relying on a privileged test owner. Because the function executes as the dedicated NOBYPASSRLS owner, this root also exercises the actual write path under the definer's restricted table grants and FORCE-RLS policy rather than succeeding only through the migration superuser. -`tests/test_document_record_idempotency_function_acl_postgres.sh` creates a run-unique `NOLOGIN`/`NOBYPASSRLS` role with schema usage but no persistence capability. It requires `has_function_privilege(..., 'EXECUTE') = false` for that role while the migration owner still retains `EXECUTE`, then directly invokes the function under `SET ROLE` with deliberately invalid null arguments and requires PostgreSQL to reject the call at function authorization. Reaching command validation would prove that the ambient `PUBLIC` grant was still effective. The probe role is removed strictly on success and best-effort on an earlier assertion failure so a cleanup error cannot erase the primary failure. +`tests/test_document_record_idempotency_function_acl_postgres.sh` verifies both sides of the capability boundary. It requires the canonical owner and executor roles to be NOLOGIN/NOSUPERUSER/NOCREATEDB/NOCREATEROLE/NOREPLICATION/NOBYPASSRLS, requires the function to be `SECURITY DEFINER` and owned by `orgmetra_document_persistence_owner`, requires schema `CREATE` to be absent after handoff, and requires the owner to have only `SELECT`/`INSERT` on the document and receipt tables. The executor must have function `EXECUTE` and schema usage but zero direct `SELECT`/`INSERT`/`UPDATE`/`DELETE`/`TRUNCATE` privilege on those tables. A behavioral `SET ROLE` probe must be denied a direct table read while an EXECUTE-only function call reaches reviewed command validation. A separate run-unique unprivileged role must still fail at function authorization, proving `PUBLIC` EXECUTE remains revoked. `tests/test_document_record_idempotency_tenant_context_postgres.sh` supplies a valid tenant-beta command while the session tenant is tenant-alpha and requires the explicit owner error `document persistence tenant context does not match requested tenant`. It also proves that the rejected attempt leaves zero beta document and receipt rows. This contract intentionally remains valid even when the Foundation database owner can bypass RLS, because the owner function itself must enforce the tenant boundary before advisory-lock acquisition. PostgreSQL 16 documents `pg_advisory_xact_lock` as an exclusive transaction-level advisory lock that waits when necessary and is automatically released at transaction end. The function is explicitly `VOLATILE`; PostgreSQL's function-volatility contract gives volatile functions a fresh snapshot for each query they execute under the ordinary Read Committed transaction model. That fresh post-lock lookup is what lets a waiting retry observe the first transaction's committed receipt rather than reinterpret a uniqueness error as success. -PostgreSQL 16 also documents that newly created functions receive `EXECUTE` for `PUBLIC` by default and recommends revoking that privilege in the same transaction when a function is not intended for every database role. Migration 0024 follows that boundary explicitly rather than relying on cluster-specific `ALTER DEFAULT PRIVILEGES` state. +PostgreSQL 16 also documents that newly created functions receive `EXECUTE` for `PUBLIC` by default and recommends revoking that privilege in the same transaction when a function is not intended for every database role. Migration 0024 follows that boundary explicitly rather than relying on cluster-specific `ALTER DEFAULT PRIVILEGES` state. The dedicated owner/executor split then ensures the eventual application-facing principal can invoke the transaction without acquiring the underlying table privileges that would permit an alternate write path. The owner function checks `transaction_isolation` before validating or mutating command state and fails closed unless it is `read committed`. `tests/test_document_record_idempotency_isolation_postgres.sh` enters a real `REPEATABLE READ` transaction and requires that isolation error before any command-field validation. Stronger isolation levels therefore cannot silently inherit semantics that depend on a fresh post-lock statement snapshot; a future successor must supply an explicit equivalent algorithm before relaxing this guard. @@ -74,12 +82,14 @@ A caller that abandons a connection mid-transaction relies on PostgreSQL rollbac The explicit Read Committed guard intentionally rejects a caller that promotes this one operation to Repeatable Read or Serializable without a successor design. That is a compatibility boundary, not an invitation to weaken isolation elsewhere: the future adapter must scope transaction policy to this documented write contract. -Revoking `PUBLIC` execution means a future non-owner application principal will not work until provisioning deliberately grants that principal `EXECUTE` on this exact function signature. That is intentional fail-closed behavior. The service-role grant must be owned with the adapter/provisioning contract and tested as a bounded capability rather than added here without an authenticated service role. +The two reserved capability-role names are cluster-level security state. A pre-existing role with either name causes migration 0024 to fail before it mutates project objects rather than attempting to reuse unknown memberships or ACLs. This means deployment migration authority must include role creation, and operators must investigate rather than rename around a collision. + +`SECURITY DEFINER` increases the importance of the fixed search path, narrow owner grants, FORCE RLS, and the executor's lack of direct table access. Any future helper called from the function must be schema-qualified and must not expand the owner's privilege set without a corresponding executable ACL regression. ## Follow-up - Admit `tests/test_document_record_idempotency_postgres.sh`, `tests/test_document_record_idempotency_function_acl_postgres.sh`, `tests/test_document_record_idempotency_isolation_postgres.sh`, and `tests/test_document_record_idempotency_tenant_context_postgres.sh` through the owner-neutral PostgreSQL Foundation registry once #310/#311 is reconciled with the document-record stack; do not add a feature-local workflow. -- Add the application/service adapter only after the `document_records` service boundary exists; it must map one external retry key and authenticated tenant context to this transaction without reimplementing replay logic, and its database principal must receive an explicit purpose-bound `EXECUTE` grant rather than inherit `PUBLIC` function access. +- Add the application/service adapter only after the `document_records` service boundary exists. Its authenticated login role must obtain purpose-bound membership in `orgmetra_document_persistence_executor`; it must not receive direct table DML or reimplement replay logic. - Re-run the full PostgreSQL acceptance on the exact protected-base head before changing this ADR from Proposed. - Keep #308 return/destruction completion receipts separate: persistence idempotency proves creation/retry identity, not later retention or destruction completion. From 3249cd2b58782e8d521a8d2bb6abbc9ae6634e23 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 20:38:27 +0900 Subject: [PATCH 56/68] docs(document-records): trace execute-only capability boundary --- .../document-record-idempotent-persistence.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/traceability/document-record-idempotent-persistence.md b/docs/traceability/document-record-idempotent-persistence.md index 9df90e110..dcc21de47 100644 --- a/docs/traceability/document-record-idempotent-persistence.md +++ b/docs/traceability/document-record-idempotent-persistence.md @@ -5,7 +5,7 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel | Requirement | Owner artifact | Executable evidence | Current state | | --- | --- | --- | --- | | One retry family has one tenant-scoped opaque key | `document_record_persist_receipt.idempotency_key` in migration 0024 | invalid/different-key behavior is constrained by the migration; #312 review remains pending | Implemented on Draft head | -| Persistence is not an ambient capability for every database role | migration 0024 revokes `EXECUTE` on `persist_document_record_once(...)` from `PUBLIC` in the same transaction that creates the function; the owner keeps its implicit capability and future service roles require an explicit purpose-bound grant | `tests/test_document_record_idempotency_function_acl_postgres.sh` creates a run-unique `NOLOGIN`/`NOBYPASSRLS` role with schema usage only, requires function EXECUTE=false for that role and true for the owner, then proves `SET ROLE` cannot enter the function body | Implemented; hosted execution pending Foundation admission | +| Persistence is an execute-only purpose-bound capability rather than an alternate direct-DML path | migration 0024 revokes `PUBLIC` EXECUTE, rejects pre-existing reserved role names before project mutation, transfers `persist_document_record_once(...)` to `orgmetra_document_persistence_owner` as `SECURITY DEFINER`, grants that NOLOGIN/NOBYPASSRLS owner only required `SELECT`/`INSERT` + helper execution, removes temporary schema `CREATE`, and grants `orgmetra_document_persistence_executor` only schema usage + function EXECUTE | `tests/test_document_record_idempotency_function_acl_postgres.sh` requires exact role attributes/function ownership/`prosecdef`, owner least privilege, executor zero table DML, behavioral denial of executor direct table access, behavioral EXECUTE-only entry into reviewed function validation, and denial for a run-unique generic probe role | Implemented; hosted execution pending Foundation admission | | Session tenant must match requested tenant before retry coordination | explicit `current_tenant_record_id() = p_tenant_record_id` guard before semantic digest/replay lock | `tests/test_document_record_idempotency_tenant_context_postgres.sh` submits a valid tenant-beta command from a tenant-alpha session, requires SQLSTATE-42501 boundary text, and proves zero beta document/receipt rows | Implemented; hosted execution pending Foundation admission | | Same key + same semantic command returns the first committed result | `persist_document_record_once(...)` semantic digest + replay branch | `tests/test_document_record_idempotency_postgres.sh` compares first and retry identity, audit/outbox references, semantic/receipt digests, and the original database-owned `recorded_at` instant; it then extracts the returned epoch and requires that exact value to equal both durable `document_record.recorded_at` and `document_record_persist_receipt.recorded_at` | Implemented; hosted execution pending Foundation admission | | Same key + changed semantics fails closed | server-side `orgmetra.document_record_persist_command.v1` SHA-256 | PostgreSQL contract changes only `application_evidence_digest_sha256` and requires the explicit semantic-conflict error | Implemented; hosted execution pending | @@ -52,9 +52,10 @@ Status: active stacked evidence for #309/#312. This file is not protected-`devel - Companion execution-order repair: `cb587679768eeb62712ca529f30d7a630817d5bd`; the companion no longer reapplies migrations and instead fails closed unless the main idempotency root has already established the owner schema/function, matching Foundation root→companion semantics. - Backend-identity RED contract: `626c62e53dfb9a4a92ad711d19699c6cb922a2ba`; the prior companion terminated by captured PID alone and used one fixed application marker, so checked backend identity was not bound through use. - Backend-identity causal repair: `8a485d60514e1a69124294c6656ffae0bb5d4f98`; recovery sessions now use UUID-derived markers, capture `backend_start`, and terminate/cleanup only an exact still-live `(pid, application_name, backend_start)` match with a `1|true` receipt. -- Persistence-function ACL RED contract: `9534af759331006b3dea2314d87903be2a718a30`; under PostgreSQL's documented defaults a fresh function is executable through `PUBLIC`, so a generic probe role would still have the capability. -- Persistence-function ACL causal fix: `18327beef7d1ca9a92479c8e8be8fe1ce16e3721`; migration 0024 revokes `EXECUTE` from `PUBLIC` inside the same transaction that creates `persist_document_record_once(...)`. -- ACL decision/evidence currentization: `4f61d3ddb2ce3afe062c8f8a5f83cf76a86f79ba`; ADR 0309 records the explicit future service-role grant boundary and PostgreSQL privilege authority. +- Persistence-function ambient-ACL RED contract: `9534af759331006b3dea2314d87903be2a718a30`; under PostgreSQL's documented defaults a fresh function is executable through `PUBLIC`, so a generic probe role would still have the capability. +- Ambient-ACL causal fix: `18327beef7d1ca9a92479c8e8be8fe1ce16e3721`; migration 0024 revokes `EXECUTE` from `PUBLIC` inside the same transaction that creates `persist_document_record_once(...)`. +- Execute-only capability RED: `12b474b5fd4218d6d9d94e5c2869b9c503035b71`; review of the callable boundary showed that a future SECURITY INVOKER service principal would need direct table privileges and could bypass the replay contract. +- Execute-only capability causal fix: `50c0259a24089cba7e848ab97887cbf0f581746e`; migration 0024 now uses fresh NOLOGIN/NOBYPASSRLS owner/executor roles, a restricted SECURITY DEFINER owner, temporary schema-CREATE handoff, and an executor with no table DML. ## Evidence limits From dd1b829a5c079749919ea929e67c7f03b45ee48b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 20:39:03 +0900 Subject: [PATCH 57/68] docs(document-records): operationalize execute-only persistence role --- docs/OPERABILITY.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/OPERABILITY.md b/docs/OPERABILITY.md index fc61b01f0..fbe4bd94d 100644 --- a/docs/OPERABILITY.md +++ b/docs/OPERABILITY.md @@ -38,13 +38,14 @@ - A successful dead-letter transition clears lease metadata, preserves the terminal failure code, moves the queue row to `dead_lettered`, and records tenant-scoped immutable escalation evidence. The escalation row is append-only and cannot be rewritten to make an operational failure disappear. - Dead-lettered rows are excluded from normal claiming. Recovery requires an explicit new business/operator action and must not mutate or reopen the terminal historical row. - Stable dispatcher worker references remain operational identities. The operator path exists only for the narrower expired-and-exhausted failure mode and does not permit takeover of live or retryable work. -- Audit/outbox SQL boundaries pin `search_path` to `pg_catalog, public, pg_temp`, the migration revokes `CREATE` on `public` from `PUBLIC`, and project objects remain in the trusted application schema until schema extraction work explicitly moves them. Normal dispatcher/persistence functions remain security-invoker boundaries; the lost-final-worker recovery function is the sole `SECURITY DEFINER` exception and is owned by the hardened NOLOGIN recovery role rather than a login or superuser role. +- Audit/outbox SQL boundaries pin `search_path` to `pg_catalog, public, pg_temp`, the migration revokes `CREATE` on `public` from `PUBLIC`, and project objects remain in the trusted application schema until schema extraction work explicitly moves them. Ordinary dispatcher/persistence functions remain security-invoker boundaries unless an ADR proves an execute-only capability cannot be preserved that way. The lost-final-worker recovery function and document-record uncertain-retry persistence function are current narrow `SECURITY DEFINER` exceptions; each is owned by its own hardened NOLOGIN/NOBYPASSRLS role rather than a login or superuser role, and its externally assignable executor/operator role has no direct DML on the protected tables. - Exponential/backoff policy selection, policy-specific producer configuration, and external delivery receipts remain release blockers before reliable asynchronous delivery is called production-ready; terminal dead-letter/escalation evidence and lost-final-worker recovery are implemented but do not by themselves prove downstream receipt. ### Document-record persistence under uncertain retry - `persist_document_record_once(...)` is the `document_records` owner boundary for an uncertain-outcome metadata-persistence retry. One transaction binds the immutable `document_record` fact to one append-only `document_record_persist_receipt`; a generic uniqueness violation, elapsed time, or connection loss is never interpreted as success evidence. -- The caller must establish `orgmetra.tenant_record_id` and it must exactly match `p_tenant_record_id` before semantic digest calculation, replay lookup, advisory-lock acquisition, or durable write. RLS remains defense in depth rather than permission to acquire another tenant's database-global coordination state. +- The callable boundary is execute-only. Migration 0024 rejects pre-existing reserved capability-role names before project mutation, creates `orgmetra_document_persistence_owner` and `orgmetra_document_persistence_executor` as NOLOGIN/NOBYPASSRLS roles, transfers the function to the restricted owner as `SECURITY DEFINER`, removes temporary schema `CREATE` before commit, and grants the executor function EXECUTE but no direct table DML. Production login roles obtain this capability only through explicit purpose-bound membership provisioning. A role-name collision is an operator-review failure, not a reason to reuse unknown role state. +- The caller must establish `orgmetra.tenant_record_id` and it must exactly match `p_tenant_record_id` before semantic digest calculation, replay lookup, advisory-lock acquisition, or durable write. FORCE RLS remains active for the dedicated function owner and defense in depth rather than permission to acquire another tenant's database-global coordination state. - The retry algorithm is intentionally restricted to PostgreSQL `READ COMMITTED`. It relies on a fresh statement snapshot after acquiring the transaction-scoped advisory lock so a waiter can observe the winner's committed receipt. `REPEATABLE READ` and `SERIALIZABLE` calls fail closed rather than pretending that stale-snapshot replay recovery is safe. - The transaction-scoped advisory lock covers only replay lookup plus authoritative database writes. OCR, LLM work, object-store transfer, network calls, long-running calculation, and other external work must complete before entering this transaction boundary; no idle external wait is allowed while the lock is held. - The semantic command digest is tenant- and purpose-bound and includes the governed persistence inputs. Function-local UTC serialization prevents equivalent `timestamptz` instants from acquiring different replay identities because of caller session timezone. @@ -52,6 +53,7 @@ - After a transport failure where commit outcome is unknown, recovery resubmits the same purpose-bound idempotency key and the exact same semantic command. A new key is a new command identity and must not be used merely to escape uncertainty. Operators escalate a conflicting replay instead of deleting or rewriting the original receipt. - The receipt is append-only, FORCE-RLS protected, and PII-minimized: it carries the opaque idempotency identity, semantic digest, document identity, receipt digest, and original database time rather than document bytes or free-form HR content. - Concurrency acceptance must observe the second PostgreSQL backend actually blocked on the first backend's advisory lock and must prove both callers converge to one durable document and one receipt. Fixed sleeps are not serialization evidence. +- Capability acceptance must prove the owner role has only the table privileges required by the function, the executor has none of those table DML privileges, direct table access as the executor is denied, and function execution still reaches reviewed command validation. A generic role with schema usage only must remain unable to execute the function. - Recovery evidence is incomplete if a test client, transaction, or temporary security principal survives the acceptance run. Test-only principals use collision-resistant per-run identities; normal completion verifies strict cleanup, while failure cleanup remains best-effort so it cannot mask the original assertion. - This retry receipt proves initial metadata persistence only. Return/destruction completion and recovery-invisibility evidence belong to the separate `document_records` lifecycle authority tracked by #308 and must not be inferred from this receipt. From 49c6f1b8b8f8b503f2c00f80fd1e676566e0d6f0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 20:39:45 +0900 Subject: [PATCH 58/68] docs(document-records): test execute-only database capability --- docs/TEST_STRATEGY.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/TEST_STRATEGY.md b/docs/TEST_STRATEGY.md index 9ba910d51..a0ff5a75f 100644 --- a/docs/TEST_STRATEGY.md +++ b/docs/TEST_STRATEGY.md @@ -31,13 +31,13 @@ The command runs Python repository-integrity validation, the dependency-free Nod | Predictive-validity study case worker/decision/evidence/criterion and recorded-time integrity | `bash tests/test_validity_study_case_postgres.sh` against PostgreSQL 16 in Foundation CI | | Performance criterion observation Job, cycle, staffing, current-recorded-time, and UTC date-boundary integrity | `bash tests/test_criterion_observation_scope_postgres.sh` against PostgreSQL 16 in Foundation CI | | Governed People mutation idempotency: tenant/route/key uniqueness, identical-command replay, changed-command rejection, rollback safety, append-only/TRUNCATE protection, forced RLS and concurrent exact-key serialization | `bash tests/test_people_mutation_idempotency_postgres.sh` against PostgreSQL 16 in Foundation CI | -| Governed `document_records` uncertain-retry persistence: purpose-bound idempotency receipt, exact semantic replay, conflicting replay rejection, database-owned result-time recovery, real advisory-lock wait graph, FORCE-RLS behavior, tenant-before-lock enforcement, unsupported-isolation fail-close, timezone-stable digest/result identity, connection cleanup, and failure-safe temporary-principal cleanup | `bash tests/test_document_record_idempotency_postgres.sh`, `bash tests/test_document_record_idempotency_tenant_context_postgres.sh`, and `bash tests/test_document_record_idempotency_isolation_postgres.sh` against PostgreSQL 16 through owner-neutral Foundation discovery | +| Governed `document_records` uncertain-retry persistence: purpose-bound idempotency receipt, exact semantic replay, conflicting replay rejection, database-owned result-time recovery, real advisory-lock wait graph, FORCE-RLS behavior, tenant-before-lock enforcement, unsupported-isolation fail-close, timezone-stable digest/result identity, connection cleanup, failure-safe temporary-principal cleanup, and an execute-only SECURITY-DEFINER capability whose executor has no direct document/receipt table DML | `bash tests/test_document_record_idempotency_postgres.sh`, `bash tests/test_document_record_idempotency_function_acl_postgres.sh`, `bash tests/test_document_record_idempotency_tenant_context_postgres.sh`, and `bash tests/test_document_record_idempotency_isolation_postgres.sh` against PostgreSQL 16 through owner-neutral Foundation discovery | | Tenant/actor/purpose authorization matrix and negative high-impact commands | service-specific unit and integration test commands recorded in each service package | | AsyncAPI/CloudEvents envelope compatibility | provider and consumer contract test commands recorded beside the versioned event schema | | External adapter timeout, malformed response, tenant mismatch, and unavailable-state handling | fake-server tests in each adapter package | | Role-workspace keyboard, focus, exact-value, permission-denied, and confirmation states | Storybook interaction/a11y tests plus browser E2E for the owning workspace | -The PostgreSQL scripts apply the checked-in migration chain required by the contract under test to a fresh database. The bitemporal and evidence-sealing tests execute concurrency regressions with an observable database barrier instead of a fixed scheduling assumption. The tenant-isolation test proves both read and write enforcement with unprivileged `NOLOGIN NOBYPASSRLS` roles, so table-owner/superuser bypass cannot manufacture a passing tenant result. The evidence-sealing test compares database output with independently precomputed canonical SHA-256 fixtures and forces a membership transaction to hold the evidence-set row lock before finalization, proving the digest snapshot includes evidence that committed first. The audit/outbox contract stores exact `AuditOutboxEvent.canonical_json()` bytes, independently verifies their SHA-256 digest in PostgreSQL, rejects extra top-level PII fields even when a caller recomputes the digest, and exercises outbox lifecycle invariants separately from immutable audit facts. The outbox-claim contract proves an already-expired lease cannot be created, verifies deterministic tenant-scoped claims return the immutable event/digest while live leases are excluded, then lets a valid one-second lease expire and requires atomic takeover of that same row with attempt count 2, a new future lease, and explicit `lease_expired` evidence. The dead-letter contract applies migrations 0001 through 0007, proves the dispatcher cannot select its own terminal attempt budget, rejects direct terminal DML before matching immutable escalation evidence and the stored budget are satisfied, exercises the real retry/claim path through the database-owned default fifth attempt, rejects retry at attempt five, lets the final lease expire, proves a replacement worker cannot create attempt six, proves the row remains bound to the recorded worker identity, rejects a foreign finalizer, permits that exact recorded identity to append terminal evidence after expiry, and rejects fabricated escalation evidence for nonterminal work. The People mutation idempotency contract applies the authoritative migration chain through 0012, verifies the replay record is written in the same transaction as its authoritative fact and audit/outbox evidence, proves rollback leaves no false replay marker, and uses concurrent exact-key sessions to prove one canonical committed identity wins without duplicate business facts. The document-record idempotency contracts apply the governed document-record chain through migration 0024. They require the requested tenant to match active tenant context before any semantic digest, replay lookup, advisory lock, or write; prove same-key/same-semantic UTC↔Asia/Seoul retries recover the original database-owned `recorded_at`; reject changed semantics; reject `REPEATABLE READ` before command work because replay visibility depends on a fresh post-lock Read Committed statement snapshot; and observe the second backend holding an ungranted advisory lock with `pg_blocking_pids(...)` naming the first before allowing the first transaction to commit. FORCE-RLS is exercised with a temporary `NOBYPASSRLS` non-superuser rather than inferred only from catalog flags. The probe identity is collision-resistant per run, normal-path cleanup is strict, failure cleanup is best-effort so it cannot hide the original assertion, and the concurrency clients must leave no PostgreSQL sessions behind. Foundation admission remains owner-neutral: the contract registry/discovery owner must include these roots without adding a filename switch. Foundation CI executes every matrix entry independently; a cancelled, skipped, queued, absent, neutral, failed, stale, predecessor-head, status-only, or model-only matrix result is not database evidence for the current head. +The PostgreSQL scripts apply the checked-in migration chain required by the contract under test to a fresh database. The bitemporal and evidence-sealing tests execute concurrency regressions with an observable database barrier instead of a fixed scheduling assumption. The tenant-isolation test proves both read and write enforcement with unprivileged `NOLOGIN NOBYPASSRLS` roles, so table-owner/superuser bypass cannot manufacture a passing tenant result. The evidence-sealing test compares database output with independently precomputed canonical SHA-256 fixtures and forces a membership transaction to hold the evidence-set row lock before finalization, proving the digest snapshot includes evidence that committed first. The audit/outbox contract stores exact `AuditOutboxEvent.canonical_json()` bytes, independently verifies their SHA-256 digest in PostgreSQL, rejects extra top-level PII fields even when a caller recomputes the digest, and exercises outbox lifecycle invariants separately from immutable audit facts. The outbox-claim contract proves an already-expired lease cannot be created, verifies deterministic tenant-scoped claims return the immutable event/digest while live leases are excluded, then lets a valid one-second lease expire and requires atomic takeover of that same row with attempt count 2, a new future lease, and explicit `lease_expired` evidence. The dead-letter contract applies migrations 0001 through 0007, proves the dispatcher cannot select its own terminal attempt budget, rejects direct terminal DML before matching immutable escalation evidence and the stored budget are satisfied, exercises the real retry/claim path through the database-owned default fifth attempt, rejects retry at attempt five, lets the final lease expire, proves a replacement worker cannot create attempt six, proves the row remains bound to the recorded worker identity, rejects a foreign finalizer, permits that exact recorded identity to append terminal evidence after expiry, and rejects fabricated escalation evidence for nonterminal work. The People mutation idempotency contract applies the authoritative migration chain through 0012, verifies the replay record is written in the same transaction as its authoritative fact and audit/outbox evidence, proves rollback leaves no false replay marker, and uses concurrent exact-key sessions to prove one canonical committed identity wins without duplicate business facts. The document-record idempotency contracts apply the governed document-record chain through migration 0024. They require the requested tenant to match active tenant context before any semantic digest, replay lookup, advisory lock, or write; prove same-key/same-semantic UTC↔Asia/Seoul retries recover the original database-owned `recorded_at`; reject changed semantics; reject `REPEATABLE READ` before command work because replay visibility depends on a fresh post-lock Read Committed statement snapshot; and observe the second backend holding an ungranted advisory lock with `pg_blocking_pids(...)` naming the first before allowing the first transaction to commit. FORCE-RLS is exercised with a temporary `NOBYPASSRLS` non-superuser rather than inferred only from catalog flags. The function-ACL contract separately proves the canonical owner/executor roles are NOLOGIN/NOBYPASSRLS, the function is owned by the restricted role as `SECURITY DEFINER`, temporary schema `CREATE` was removed, the owner has only required table SELECT/INSERT, the executor has EXECUTE but no table DML, direct executor table access fails, an executor function call reaches reviewed command validation, and a generic run-unique role cannot execute the function. The probe identity is collision-resistant per run, normal-path cleanup is strict, failure cleanup is best-effort so it cannot hide the original assertion, and the concurrency clients must leave no PostgreSQL sessions behind. Foundation admission remains owner-neutral: the contract registry/discovery owner must include these roots without adding a filename switch. Foundation CI executes every matrix entry independently; a cancelled, skipped, queued, absent, neutral, failed, stale, predecessor-head, status-only, or model-only matrix result is not database evidence for the current head. Future service packages must publish their exact test, statement-coverage, branch-coverage, docstring, typecheck, and build commands in the package manifest and CI log. @@ -55,6 +55,7 @@ Required negative and provenance tests include: - `document_records` persistence must bind active tenant context before database-global advisory coordination, recover same-key/same-semantic uncertain retries to the exact original receipt/result, and reject same-key changed semantics rather than treating a uniqueness error or elapsed time as success evidence; - the document-record retry path must fail closed outside Read Committed, and real concurrency acceptance must observe PostgreSQL's advisory wait graph rather than rely on a scheduling delay; - document-record retry evidence must return the original database-owned time and remain invariant across caller session timezones; test-only RLS principals and concurrent clients must be fully cleaned up on a passing run; +- the document-record application-facing database capability must be function EXECUTE only: its hardened NOLOGIN/NOBYPASSRLS function owner may hold the minimum direct SELECT/INSERT needed by the SECURITY-DEFINER implementation, while the executor role must have no direct persistence-table DML and no schema CREATE; - previewed evidence versions must equal recorded evidence versions; - an open evidence set rejects a caller-supplied digest, preventing a client assertion from masquerading as database-observed membership; - finalizing a selection decision requires at least one versioned evidence member, computes the canonical SHA-256 digest in PostgreSQL, and seals exactly one evidence set in the same transaction; From 3d4fff0f0c652aa65887c0f3e12860beb2759f81 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 20:47:58 +0900 Subject: [PATCH 59/68] docs(document-records): secure execute-only persistence capability --- docs/SECURITY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/SECURITY.md b/docs/SECURITY.md index fd6dd3ea6..b6695fabf 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -25,6 +25,7 @@ - A dispatcher lease is an executable capability. Completion and retry require the exact owner of a still-live lease under the active tenant context. Before retry-budget exhaustion, expired ownership must be reclaimed through the guarded claim path; after exhaustion, claim/retry cannot create attempt N+1 and only the exact recorded stable worker reference may use the normal worker terminalization path. If that recorded final worker identity is permanently unavailable, a separately provisioned purpose-bound operator capability may terminalize only an already-expired exhausted lease through the audited recovery function; the operator role has no direct outbox read/write or escalation-insert privilege. - Terminal dead-lettering additionally requires an immutable database-owned retry budget, durable exhaustion of that budget, a bounded failure classification, and matching opaque escalation evidence. A dispatcher cannot lower the threshold at finalization, structurally valid direct terminal DML cannot omit the evidence/budget invariant, a foreign worker cannot steal an exhausted row, a dead-lettered row cannot silently re-enter normal dispatch, and its escalation evidence cannot be updated or deleted. - Privileged outbox recovery role names are fail-closed deployment identities. Migration 0008 rejects either reserved role name if it already exists instead of inheriting unknown memberships or ACLs; fresh NOLOGIN/NOBYPASSRLS roles are created only after that preflight. The temporary `CREATE` privilege needed for function ownership transfer is granted and revoked inside one transaction, so an interrupted handoff cannot strand schema-creation authority. +- Document-record uncertain-retry persistence is also an explicit database capability. Migration 0024 rejects pre-existing reserved persistence-role names before changing project objects, owns `persist_document_record_once(...)` with a dedicated NOLOGIN/NOBYPASSRLS `SECURITY DEFINER` role, and grants the externally assignable executor only schema usage plus function `EXECUTE`. The executor receives no direct `SELECT`, `INSERT`, `UPDATE`, `DELETE`, or `TRUNCATE` privilege on `document_record` or `document_record_persist_receipt`, so a service role cannot bypass semantic-digest comparison, advisory-lock serialization, tenant-before-lock validation, or receipt creation with direct DML. The function owner receives schema `CREATE` only for the ownership handoff and loses it before commit. - Credentials and passkeys remain in Keyverse or external secret managers. - Service database roles cannot query another service's application tables. - Client error responses expose a random `support_reference`, never an internal trace/span identifier or encoded infrastructure context. From ae990394fec8f1caf46f0d8e1adca2d25a483de9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 20:48:21 +0900 Subject: [PATCH 60/68] docs(document-records): model persistence capability threats --- docs/THREAT_MODEL.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index 29d11a478..86473e3d3 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -5,12 +5,12 @@ | Threat | Example | Preventive control | Detection evidence and required test | |---|---|---|---| | Spoofing | External identity treated as HR person identity | Separate `person_record` from the Keyverse subject; verify issuer, audience, tenant, actor binding, and token lifetime. | Authentication-denial audit; tests reject subject/person substitution and stale authorization. | -| Tampering | Selection evidence, audit envelope, terminal escalation evidence, or delivery history changed after decision | Append-only decision/evidence records; database-sealed evidence digest; append-only `audit_event_record` and `outbox_delivery_escalation_record`; immutable database-owned delivery retry budget; database recomputation of SHA-256 over exact canonical audit bytes; guarded delivery state stored separately. | Integrity alert; tests reject update/delete, digest mismatch, added audit fields, illegal outbox state changes, retry-budget mutation/dispatcher override, attempt N+1, terminal-row reopening, escalation mutation/fabrication, and version mismatch. | -| Repudiation | Hiring manager or integration operator denies a decision or terminal delivery failure | Human confirmation reference, actor, tenant, purpose, reason, evidence versions, immutable audit event, recorded dispatcher identity, and immutable terminal escalation reference with durable attempt count. | Correlated decision/audit/escalation lookup; tests prove actor traceability, prohibit high-impact audit persistence without confirmation, and preserve one append-only escalation record for dead-lettered work. | -| Information disclosure | PII broadcast, over-broad field response, or retained data through an event bus or failure queue | Opaque references, exact durable audit-field allowlist, purpose-bound tenant/resource/operation/scope/field authorization before protected values leave the HR boundary, tenant-scoped encryption, and no copied mutable HR payload in audit or escalation evidence. | Payload scanner plus authorization/database contracts; tests reject extra employee-name/PII event fields, foreign-resource access, missing operation scope, disallowed fields, and malformed or wildcard-like authorization attributes while constraining escalation metadata to governance codes/references. | -| Cross-tenant access | Tenant A reads, reconstructs, changes, or emits evidence for Tenant B HRIS facts by altering a path, header, reference, cache key, event, delivery, escalation, or by supplying a foreign fact with a colliding durable identifier to an in-memory decision. | Authenticated tenant context, explicit agreement among request/actor/resource/policy tenants, explicit tenant scope in historical reconstruction and HRIS decision functions, audit event/tenant identity binding, forced RLS on audit/outbox/escalation relations, service-owned database roles, tenant-aware cache keys, and consumer-side event validation. | `cross_tenant_access_denied` audit event with no sensitive values; authorization/integration/kernel/database tests attempt request/actor/resource tenant mismatches, direct reads/writes, object-reference swaps, colliding identifiers, event tenant mismatch, foreign delivery finalization/escalation, cache poisoning, and replay across tenants and require denial or exclusion with unchanged target data. | -| Denial of service | Integration retries flood services, a permanently rejected event loops forever, or final-attempt ownership is lost | Idempotency, guarded outbox leasing, immutable bounded database-owned retry-attempt budget with terminal dead-letter removal, no claim/retry after exhaustion, stable final-attempt worker identity, bounded exponential backoff and queue limits before production dispatcher release, circuit breaking, and per-tenant budgets. | Queue-depth/lease/retry/dead-letter telemetry; load and recovery tests must prove bounded work, pre-exhaustion expired-lease recovery, stored retry-budget exhaustion, no attempt N+1, exhausted-final-lease non-reclaimability, recorded-owner terminalization, and fair tenant isolation before dispatcher release. | -| Elevation of privilege | A purpose header, broad token, LLM, or foreign dispatcher grants itself access or finalizes another worker's high-impact/transport state | Purpose-bound authorization requires exact tenant/resource/purpose/operation matching, operation-specific Keyverse scope, and field minimization; LLM outputs remain draft evidence; human confirmation controls HR writes; dispatcher completion/retry require exact live lease ownership; dead-lettering requires exhausted durable budget plus the exact recorded final-attempt worker identity, and never accepts a dispatcher-selected retry threshold. | Authorization-denial audit; tests reject purpose-only authorization, missing scopes, disallowed fields, cross-tenant resources, LLM decision records, missing-confirmation audit events, `Offered`/`Worker` transitions, foreign/stale completion or retry, dispatcher retry-budget override, direct premature terminal DML, replacement-worker claim after exhaustion, foreign dead-lettering, fabricated nonterminal escalation evidence, and premature dead-lettering. | +| Tampering | Selection evidence, audit envelope, terminal escalation evidence, delivery history, or a document-persistence retry receipt changed after decision | Append-only decision/evidence records; database-sealed evidence digest; append-only `audit_event_record`, `outbox_delivery_escalation_record`, and `document_record_persist_receipt`; immutable database-owned delivery retry budget; database recomputation of SHA-256 over exact canonical audit/document evidence bytes; guarded delivery state stored separately. | Integrity alert; tests reject update/delete/TRUNCATE, digest mismatch, added audit fields, illegal outbox state changes, retry-budget mutation/dispatcher override, attempt N+1, terminal-row reopening, escalation mutation/fabrication, document-receipt rewrite, changed-command idempotency replay, and version mismatch. | +| Repudiation | Hiring manager or integration operator denies a decision, terminal delivery failure, or committed document-metadata write after the client lost the response | Human confirmation reference, actor, tenant, purpose, reason, evidence versions, immutable audit event, recorded dispatcher identity, immutable terminal escalation reference with durable attempt count, and an immutable tenant-scoped document persistence receipt that returns the first committed identity/time on exact replay. | Correlated decision/audit/escalation/receipt lookup; tests prove actor traceability, prohibit high-impact audit persistence without confirmation, preserve one append-only escalation record for dead-lettered work, and recover one authoritative document result after observed commit plus caller connection loss. | +| Information disclosure | PII broadcast, over-broad field response, or retained data through an event bus, failure queue, or retry receipt | Opaque references, exact durable audit-field allowlist, purpose-bound tenant/resource/operation/scope/field authorization before protected values leave the HR boundary, tenant-scoped encryption, and no copied mutable HR payload in audit, escalation, or idempotency-receipt evidence. | Payload scanner plus authorization/database contracts; tests reject extra employee-name/PII event fields, foreign-resource access, missing operation scope, disallowed fields, and malformed or wildcard-like authorization attributes while constraining escalation and replay receipts to governance codes/references/digests. | +| Cross-tenant access | Tenant A reads, reconstructs, changes, emits evidence for, or acquires retry coordination over Tenant B HRIS facts by altering a path, header, reference, cache key, event, delivery, escalation, idempotency key, or by supplying a foreign fact with a colliding durable identifier to an in-memory decision. | Authenticated tenant context, explicit agreement among request/actor/resource/policy tenants, explicit tenant scope in historical reconstruction and HRIS decision functions, tenant-before-advisory-lock validation for document retry, audit event/tenant identity binding, forced RLS on audit/outbox/escalation/document-receipt relations, service-owned database roles, tenant-aware cache keys, and consumer-side event validation. | `cross_tenant_access_denied` audit event with no sensitive values; authorization/integration/kernel/database tests attempt request/actor/resource tenant mismatches, direct reads/writes, object-reference swaps, colliding identifiers, event tenant mismatch, foreign delivery finalization/escalation, cross-tenant retry-lock acquisition, cache poisoning, and replay across tenants and require denial or exclusion with unchanged target data. | +| Denial of service | Integration retries flood services, a permanently rejected event loops forever, a final-attempt owner disappears, or a caller intentionally holds another tenant's retry key | Idempotency, guarded outbox leasing, immutable bounded database-owned retry-attempt budget with terminal dead-letter removal, no claim/retry after exhaustion, stable final-attempt worker identity, tenant validation before database-global advisory locks, short database-only retry transactions, bounded exponential backoff and queue limits before production dispatcher release, circuit breaking, and per-tenant budgets. | Queue-depth/lease/retry/dead-letter/advisory-wait telemetry; load and recovery tests must prove bounded work, pre-exhaustion expired-lease recovery, stored retry-budget exhaustion, no attempt N+1, exhausted-final-lease non-reclaimability, recorded-owner terminalization, foreign-tenant retry denial before lock acquisition, and fair tenant isolation before dispatcher release. | +| Elevation of privilege | A purpose header, broad token, LLM, foreign dispatcher, or database service role grants itself access, bypasses idempotency with direct DML, or finalizes another worker's high-impact/transport state | Purpose-bound authorization requires exact tenant/resource/purpose/operation matching, operation-specific Keyverse scope, and field minimization; LLM outputs remain draft evidence; human confirmation controls HR writes; dispatcher completion/retry require exact live lease ownership; dead-lettering requires exhausted durable budget plus the exact recorded final-attempt worker identity; document uncertain-retry persistence uses a restricted NOLOGIN/NOBYPASSRLS `SECURITY DEFINER` owner while the externally assignable executor has function EXECUTE but no direct persistence-table DML or schema CREATE. | Authorization/ACL-denial evidence; tests reject purpose-only authorization, missing scopes, disallowed fields, cross-tenant resources, LLM decision records, missing-confirmation audit events, `Offered`/`Worker` transitions, foreign/stale completion or retry, dispatcher retry-budget override, direct premature terminal DML, replacement-worker claim after exhaustion, foreign dead-lettering, fabricated nonterminal escalation evidence, premature dead-lettering, generic function execution, executor direct document-table access, over-privileged function ownership, and reserved-role-name reuse. | ## Model risk @@ -21,3 +21,5 @@ LLM analysis may summarize, extract, or draft but cannot publish job profiles, a Blanket masking can break HR work. Orgmetra uses purpose-bound access, encryption, retention, audit, and export control so authorized users can work with required PII safely. The purpose-bound decision evaluates request, authenticated actor, resource, and policy tenant identity before resource/purpose/operation/scope/field attributes; decisions retain governance metadata and field names rather than protected field values. Event and telemetry surfaces minimize PII even when the authoritative service is permitted to display it. `audit_event_record` keeps only the allowlisted governance envelope; delivery retries, leases, immutable retry budget, and terminal state live in `outbox_delivery_record`; immutable dead-letter escalation metadata lives in `outbox_delivery_escalation_record`. This separation prevents mutable transport coordination or terminal operator evidence from rewriting or expanding retained audit evidence. Exhausted final-attempt ownership remains bound to the recorded stable worker reference under the normal worker path; if that identity is permanently unavailable, the implemented `orgmetra_outbox_operator` capability may invoke only the guarded expired-final-lease recovery function while remaining unable to read/update outbox rows or insert escalation evidence directly. + +`document_record_persist_receipt` stores only tenant identity, an opaque purpose-scoped retry key, semantic/result digests, the committed document identity, and database time. The callable write surface is `persist_document_record_once(...)`; its dedicated function owner can perform only the required document/receipt SELECT/INSERT under FORCE RLS, while `orgmetra_document_persistence_executor` can invoke that function but cannot read or mutate either persistence table directly. Reserved owner/executor role-name collisions fail before project mutation instead of reusing unknown cluster ACL state. From 8e86c42d5545b9025341dca9afc220666ab56c93 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 20:51:49 +0900 Subject: [PATCH 61/68] fix(document-records): reseal execute-only persistence manifest --- manifest.json | 584 +------------------------------------------------- 1 file changed, 1 insertion(+), 583 deletions(-) diff --git a/manifest.json b/manifest.json index d9e940dd1..737874e2e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,583 +1 @@ -{ - "package": "orgmetra-foundation-pack", - "version": "0.1.0", - "generated_for_branch": "feat/audit-outbox-envelope", - "files": [ - { - "path": ".github/workflows/foundation-ci.yml", - "sha256": "b6a4365936b66803a8112f034c77d53d33301a7a798ed4f68746a4f2d8b081d7", - "bytes": 6651, - "lines": 125 - }, - { - "path": ".gitignore", - "sha256": "145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21", - "bytes": 375, - "lines": 37 - }, - { - "path": "AGENTS.md", - "sha256": "28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16", - "bytes": 2246, - "lines": 34 - }, - { - "path": "ARCHITECTURE.md", - "sha256": "52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850", - "bytes": 7864, - "lines": 107 - }, - { - "path": "CHANGELOG.md", - "sha256": "321c43f388dd561b8867684daac74b0c21f3676d66e15f941feed28d5cf02459", - "bytes": 17829, - "lines": 78 - }, - { - "path": "CLAUDE.md", - "sha256": "add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f", - "bytes": 1229, - "lines": 20 - }, - { - "path": "LICENSE", - "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", - "bytes": 11358, - "lines": 202 - }, - { - "path": "NOTICE", - "sha256": "34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042", - "bytes": 305, - "lines": 4 - }, - { - "path": "README.md", - "sha256": "1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6", - "bytes": 3785, - "lines": 81 - }, - { - "path": "database/migrations/0001_foundation_schema.sql", - "sha256": "ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd", - "bytes": 38747, - "lines": 916 - }, - { - "path": "database/migrations/0002_sealed_evidence_digest.sql", - "sha256": "93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c", - "bytes": 6649, - "lines": 202 - }, - { - "path": "database/migrations/0003_audit_outbox_persistence.sql", - "sha256": "2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc", - "bytes": 15417, - "lines": 423 - }, - { - "path": "database/migrations/0004_outbox_delivery_claim.sql", - "sha256": "d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef", - "bytes": 9451, - "lines": 234 - }, - { - "path": "database/migrations/0005_outbox_delivery_finalization.sql", - "sha256": "b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961", - "bytes": 6125, - "lines": 170 - }, - { - "path": "database/migrations/0006_outbox_delivery_dead_letter.sql", - "sha256": "c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7", - "bytes": 24919, - "lines": 628 - }, - { - "path": "database/migrations/0007_outbox_retry_exhaustion.sql", - "sha256": "812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5", - "bytes": 19081, - "lines": 476 - }, - { - "path": "database/migrations/0008_audit_outbox_review_hardening.sql", - "sha256": "c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b", - "bytes": 17562, - "lines": 448 - }, - { - "path": "database/migrations/0009_candidate_worker_conversion_governance.sql", - "sha256": "4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9", - "bytes": 11537, - "lines": 281 - }, - { - "path": "database/migrations/0010_validity_study_case_integrity.sql", - "sha256": "3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1", - "bytes": 11979, - "lines": 313 - }, - { - "path": "database/migrations/0011_criterion_observation_scope.sql", - "sha256": "f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9", - "bytes": 7444, - "lines": 165 - }, - { - "path": "database/migrations/0012_people_mutation_idempotency.sql", - "sha256": "52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69", - "bytes": 3162, - "lines": 76 - }, - { - "path": "database/migrations/0013_job_analysis_snapshot.sql", - "sha256": "b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee", - "bytes": 12713, - "lines": 260 - }, - { - "path": "database/migrations/0021_document_record_persistence.sql", - "sha256": "e2dd9ca0c17141c2b3e06f64726f3ac0fa7cb1cd02798564cef72a12455a1286", - "bytes": 14001, - "lines": 334 - }, - { - "path": "database/migrations/0022_document_record_evidence_unique_keys.sql", - "sha256": "716960d224f1d2feabebc9302db93d7c2dd945978bbc806663eacc27f8606d73", - "bytes": 631, - "lines": 15 - }, - { - "path": "database/migrations/0023_document_record_canonical_encoding.sql", - "sha256": "9600bd071c39d904c041cf8037add92e582af577b2edc0de0f39801095dca195", - "bytes": 4364, - "lines": 87 - }, - { - "path": "database/migrations/0024_document_record_idempotent_persistence.sql", - "sha256": "502637f43b166f16be355a3f159f10151c61dfdd9b2849d4a5d38cc73404589f", - "bytes": 14343, - "lines": 340 - }, - { - "path": "docs/API_CONTRACT.md", - "sha256": "63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589", - "bytes": 4555, - "lines": 76 - }, - { - "path": "docs/DATA_MODEL.md", - "sha256": "6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a", - "bytes": 13366, - "lines": 85 - }, - { - "path": "docs/ERD.md", - "sha256": "546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe", - "bytes": 6964, - "lines": 70 - }, - { - "path": "docs/OPERABILITY.md", - "sha256": "591c765b107f84e76b3f380adfafc3fec67c0735e6d3f33431369659f4f28c15", - "bytes": 14597, - "lines": 85 - }, - { - "path": "docs/PRD.md", - "sha256": "3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1", - "bytes": 5490, - "lines": 111 - }, - { - "path": "docs/SECURITY.md", - "sha256": "01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac", - "bytes": 11185, - "lines": 64 - }, - { - "path": "docs/STORYBOARD.md", - "sha256": "6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2", - "bytes": 1342, - "lines": 28 - }, - { - "path": "docs/STORYBOOK.md", - "sha256": "82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9", - "bytes": 1389, - "lines": 50 - }, - { - "path": "docs/TEST_STRATEGY.md", - "sha256": "48596f3258300feedaa42bedc89ac59ef8775a321d323243c1d29ef52e5db37e", - "bytes": 19089, - "lines": 139 - }, - { - "path": "docs/THREAT_MODEL.md", - "sha256": "f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252", - "bytes": 6736, - "lines": 23 - }, - { - "path": "docs/TRACEABILITY.md", - "sha256": "dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e", - "bytes": 11462, - "lines": 40 - }, - { - "path": "docs/TRD.md", - "sha256": "23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077", - "bytes": 9064, - "lines": 101 - }, - { - "path": "docs/UML.md", - "sha256": "fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9", - "bytes": 5528, - "lines": 122 - }, - { - "path": "docs/USER_STORIES.md", - "sha256": "5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f", - "bytes": 2670, - "lines": 37 - }, - { - "path": "docs/WIREFRAMES.md", - "sha256": "b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e", - "bytes": 2005, - "lines": 77 - }, - { - "path": "docs/adr/0001-orgmetra-authoritative-hris-record.md", - "sha256": "0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572", - "bytes": 6108, - "lines": 53 - }, - { - "path": "docs/adr/0002-federated-cwl-integration-boundaries.md", - "sha256": "b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2", - "bytes": 4072, - "lines": 44 - }, - { - "path": "docs/adr/0003-bitemporal-hris-data-contract.md", - "sha256": "d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799", - "bytes": 4453, - "lines": 47 - }, - { - "path": "docs/adr/0004-employment-position-version-and-assignment-binding.md", - "sha256": "fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182", - "bytes": 1872, - "lines": 30 - }, - { - "path": "docs/adr/0005-exclusive-employment-and-staffable-seats.md", - "sha256": "10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b", - "bytes": 2091, - "lines": 34 - }, - { - "path": "docs/adr/0006-governed-audit-outbox-envelope.md", - "sha256": "827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd", - "bytes": 14100, - "lines": 66 - }, - { - "path": "docs/adr/0007-governed-job-analysis-evidence.md", - "sha256": "953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52", - "bytes": 5653, - "lines": 57 - }, - { - "path": "docs/adr/0008-purpose-bound-pii-authorization.md", - "sha256": "c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7", - "bytes": 5988, - "lines": 55 - }, - { - "path": "docs/adr/0009-performance-criterion-observation-scope.md", - "sha256": "1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64", - "bytes": 7057, - "lines": 57 - }, - { - "path": "docs/adr/0010-naruon-calendar-intent-boundary.md", - "sha256": "3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9", - "bytes": 3917, - "lines": 35 - }, - { - "path": "docs/adr/0011-bitemporal-workforce-composition.md", - "sha256": "dbe96dfd47066288cec835789de54cc4293f920d2ad4b0e0dba930191d7d249b", - "bytes": 5551, - "lines": 53 - }, - { - "path": "docs/adr/0012-governed-migration-handoff.md", - "sha256": "c7bfbda34996f717ed31f8307acc16a5d69ae464edb184ab5c8ec4b2d5763cbc", - "bytes": 5958, - "lines": 59 - }, - { - "path": "docs/adr/0013-governed-requisition-review-packet.md", - "sha256": "70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802", - "bytes": 4693, - "lines": 46 - }, - { - "path": "docs/adr/0014-job-analysis-snapshot-persistence.md", - "sha256": "a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105", - "bytes": 5365, - "lines": 49 - }, - { - "path": "docs/adr/0107-document-record-persistence.md", - "sha256": "04dcfa124d8cfcb2dc1527fbafaf82a217a0471f5aa27392e52ce5dfcea54810", - "bytes": 6448, - "lines": 48 - }, - { - "path": "docs/adr/0309-document-record-idempotent-persistence.md", - "sha256": "910b544a3be0f9c6f41893e1e78f7362ff53e36b053f26112a96a4e7bcb2ad11", - "bytes": 13534, - "lines": 94 - }, - { - "path": "docs/adr/README.md", - "sha256": "f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002", - "bytes": 1838, - "lines": 18 - }, - { - "path": "docs/doctoring/REFERENCES.md", - "sha256": "929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5", - "bytes": 6352, - "lines": 69 - }, - { - "path": "docs/doctoring/document-record-persistence-references.md", - "sha256": "e371afc5b15351682b66477e48654554704a62fa9db5fd134767d47ce8d88b36", - "bytes": 2038, - "lines": 23 - }, - { - "path": "docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md", - "sha256": "b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd", - "bytes": 8227, - "lines": 226 - }, - { - "path": "docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md", - "sha256": "4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d", - "bytes": 6237, - "lines": 187 - }, - { - "path": "docs/traceability/document-record-idempotent-persistence.md", - "sha256": "310068058910f921109c295730e0fd41fa3cccde4d403b8610cbed0a82d9947e", - "bytes": 12809, - "lines": 65 - }, - { - "path": "docs/traceability/document-record-persistence.md", - "sha256": "3849ab6642f8b171aae6b379781fb19028635996428fbc37788334bd1feb167a", - "bytes": 4120, - "lines": 30 - }, - { - "path": "package.json", - "sha256": "59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5", - "bytes": 388, - "lines": 9 - }, - { - "path": "packages/hris-kernel/src/orgmetra_hris_kernel/audit.py", - "sha256": "3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190", - "bytes": 7707, - "lines": 160 - }, - { - "path": "packages/hris-kernel/tests/test_audit_outbox.py", - "sha256": "5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c", - "bytes": 7556, - "lines": 200 - }, - { - "path": "schemas/openapi.yaml", - "sha256": "09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f", - "bytes": 29503, - "lines": 1020 - }, - { - "path": "scripts/foundation-contract-core.mjs", - "sha256": "aa1965c7101551570a64c9e679c2f9042eb449173fa82face5e94e9c72ded8d1", - "bytes": 29278, - "lines": 706 - }, - { - "path": "scripts/foundation-contract.mjs", - "sha256": "5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a", - "bytes": 218, - "lines": 6 - }, - { - "path": "tests/dispatcher-inventory.test.mjs", - "sha256": "09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261", - "bytes": 1597, - "lines": 34 - }, - { - "path": "tests/document_record_idempotency_postcommit_recovery_companion.sh", - "sha256": "ecbde2131fc5341dc11017305d0bb114136dd1289a05ece14e7c3080a8a52e93", - "bytes": 11193, - "lines": 277 - }, - { - "path": "tests/foundation-contract.test.mjs", - "sha256": "648533b4aff8cee643df4afc06b463eda788e002e11d043971c8a16804c68501", - "bytes": 14943, - "lines": 387 - }, - { - "path": "tests/openapi-contract.test.mjs", - "sha256": "80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc", - "bytes": 6438, - "lines": 195 - }, - { - "path": "tests/test_audit_outbox_hardening_postgres.sh", - "sha256": "518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0", - "bytes": 13396, - "lines": 333 - }, - { - "path": "tests/test_audit_outbox_postgres.sh", - "sha256": "e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2", - "bytes": 13443, - "lines": 357 - }, - { - "path": "tests/test_bitemporal_postgres.sh", - "sha256": "7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc", - "bytes": 8209, - "lines": 230 - }, - { - "path": "tests/test_candidate_worker_conversion_postgres.sh", - "sha256": "681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90", - "bytes": 14673, - "lines": 344 - }, - { - "path": "tests/test_criterion_observation_scope_postgres.sh", - "sha256": "0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d", - "bytes": 17811, - "lines": 469 - }, - { - "path": "tests/test_document_record_canonical_bytes_postgres.sh", - "sha256": "10b12f2f7afeb04fc04182170d2d00bca69b4f0f51c604ee565308b906d8c550", - "bytes": 8394, - "lines": 177 - }, - { - "path": "tests/test_document_record_evidence_unique_keys_postgres.sh", - "sha256": "d678b4ee7f1c69c16632f188ae8f7b5c717ecc55e49dc09d7889ebe02ed7212a", - "bytes": 5058, - "lines": 103 - }, - { - "path": "tests/test_document_record_idempotency_function_acl_postgres.sh", - "sha256": "ba2f60295d1cbed7eb773ece4a79e11b6b186df4301a440d0c96e407663e3999", - "bytes": 3500, - "lines": 123 - }, - { - "path": "tests/test_document_record_idempotency_isolation_postgres.sh", - "sha256": "a8e7a099e9b046753591167ddfe4e4206bccd2f00f182925720fa3c15b880a32", - "bytes": 1873, - "lines": 62 - }, - { - "path": "tests/test_document_record_idempotency_postcommit_recovery_contract.py", - "sha256": "69cea86ef064c7bc8135d1e28f8dfd6eb893fdd6ee8b314045492971adc95aa1", - "bytes": 2318, - "lines": 68 - }, - { - "path": "tests/test_document_record_idempotency_postgres.sh", - "sha256": "db078f2d5876e844d455915d3446dc8c25ca8ba95cac87481c26911f839e246d", - "bytes": 17324, - "lines": 413 - }, - { - "path": "tests/test_document_record_idempotency_tenant_context_postgres.sh", - "sha256": "171f35d16891ac58d67a54504ce051a4bff0fedbf7ab43bed2f9ef3f3765b945", - "bytes": 5830, - "lines": 140 - }, - { - "path": "tests/test_document_record_persistence_postgres.sh", - "sha256": "f3d5fcb83a406ac202986f0272f673a8f3c6349752119d27b4d42e4c6bbe7072", - "bytes": 17015, - "lines": 376 - }, - { - "path": "tests/test_evidence_sealing_postgres.sh", - "sha256": "57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7", - "bytes": 11349, - "lines": 370 - }, - { - "path": "tests/test_job_analysis_snapshot_postgres.sh", - "sha256": "ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f", - "bytes": 13542, - "lines": 296 - }, - { - "path": "tests/test_operational_uuid_postgres.sh", - "sha256": "7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7", - "bytes": 3346, - "lines": 101 - }, - { - "path": "tests/test_outbox_claim_postgres.sh", - "sha256": "1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b", - "bytes": 14817, - "lines": 429 - }, - { - "path": "tests/test_outbox_dead_letter_postgres.sh", - "sha256": "0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d", - "bytes": 14008, - "lines": 377 - }, - { - "path": "tests/test_people_mutation_idempotency_postgres.sh", - "sha256": "3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5", - "bytes": 16191, - "lines": 381 - }, - { - "path": "tests/test_tenant_isolation_postgres.sh", - "sha256": "dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a", - "bytes": 15134, - "lines": 388 - }, - { - "path": "tests/test_validity_study_case_postgres.sh", - "sha256": "0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02", - "bytes": 14708, - "lines": 301 - }, - { - "path": "tests/validate_repository.py", - "sha256": "6c39b7e25ed127b34a74532ae5e607943b1f73b964bc931f256dc99df75f6b54", - "bytes": 28431, - "lines": 655 - } - ] -} +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"b6a4365936b66803a8112f034c77d53d33301a7a798ed4f68746a4f2d8b081d7","bytes":6651,"lines":125},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"321c43f388dd561b8867684daac74b0c21f3676d66e15f941feed28d5cf02459","bytes":17829,"lines":78},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"database/migrations/0021_document_record_persistence.sql","sha256":"e2dd9ca0c17141c2b3e06f64726f3ac0fa7cb1cd02798564cef72a12455a1286","bytes":14001,"lines":334},{"path":"database/migrations/0022_document_record_evidence_unique_keys.sql","sha256":"716960d224f1d2feabebc9302db93d7c2dd945978bbc806663eacc27f8606d73","bytes":631,"lines":15},{"path":"database/migrations/0023_document_record_canonical_encoding.sql","sha256":"9600bd071c39d904c041cf8037add92e582af577b2edc0de0f39801095dca195","bytes":4364,"lines":87},{"path":"database/migrations/0024_document_record_idempotent_persistence.sql","sha256":"31b25665cd2a4be256d0fa3e392fdbfc7e7f87c51721dfa4f5d0c6bec2dbbbaa","bytes":17124,"lines":394},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"8db14e56a3c358b8ce97532862d70a440d9141459e6e4e81fa587a998d684d22","bytes":15891,"lines":87},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"9b165006893930266b3af598468219931bfd6fa2f52896ba4d2ffb55bcb2e4b0","bytes":11986,"lines":65},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"8b24cc0af5b5f5040cfd0052ec9f27452e9afb47969d5b717a81b5bcac466fe3","bytes":20040,"lines":140},{"path":"docs/THREAT_MODEL.md","sha256":"95a2367829820a1c7732e292adb89a38be55452f60e7bf3c6caa347450de6ec0","bytes":8595,"lines":25},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"dbe96dfd47066288cec835789de54cc4293f920d2ad4b0e0dba930191d7d249b","bytes":5551,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"c7bfbda34996f717ed31f8307acc16a5d69ae464edb184ab5c8ec4b2d5763cbc","bytes":5958,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/0107-document-record-persistence.md","sha256":"04dcfa124d8cfcb2dc1527fbafaf82a217a0471f5aa27392e52ce5dfcea54810","bytes":6448,"lines":48},{"path":"docs/adr/0309-document-record-idempotent-persistence.md","sha256":"9e0bf26886b246956b2bcb540ff5d9e4e0d5e480d578c0f6c6d0951cb0415ec0","bytes":16322,"lines":104},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/document-record-persistence-references.md","sha256":"e371afc5b15351682b66477e48654554704a62fa9db5fd134767d47ce8d88b36","bytes":2038,"lines":23},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"docs/traceability/document-record-idempotent-persistence.md","sha256":"c585a24a744a41ca75da0f9e2e14ad354841bd24bad9e06660005bd4564767bf","bytes":13411,"lines":66},{"path":"docs/traceability/document-record-persistence.md","sha256":"3849ab6642f8b171aae6b379781fb19028635996428fbc37788334bd1feb167a","bytes":4120,"lines":30},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"aa1965c7101551570a64c9e679c2f9042eb449173fa82face5e94e9c72ded8d1","bytes":29278,"lines":706},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/document_record_idempotency_postcommit_recovery_companion.sh","sha256":"ecbde2131fc5341dc11017305d0bb114136dd1289a05ece14e7c3080a8a52e93","bytes":11193,"lines":277},{"path":"tests/foundation-contract.test.mjs","sha256":"648533b4aff8cee643df4afc06b463eda788e002e11d043971c8a16804c68501","bytes":14943,"lines":387},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_document_record_canonical_bytes_postgres.sh","sha256":"10b12f2f7afeb04fc04182170d2d00bca69b4f0f51c604ee565308b906d8c550","bytes":8394,"lines":177},{"path":"tests/test_document_record_evidence_unique_keys_postgres.sh","sha256":"d678b4ee7f1c69c16632f188ae8f7b5c717ecc55e49dc09d7889ebe02ed7212a","bytes":5058,"lines":103},{"path":"tests/test_document_record_idempotency_function_acl_postgres.sh","sha256":"81b0587ea27101bc57553de9c7da09e8cccb93807dff1128d17df263085e9714","bytes":10268,"lines":247},{"path":"tests/test_document_record_idempotency_isolation_postgres.sh","sha256":"a8e7a099e9b046753591167ddfe4e4206bccd2f00f182925720fa3c15b880a32","bytes":1873,"lines":62},{"path":"tests/test_document_record_idempotency_postcommit_recovery_contract.py","sha256":"69cea86ef064c7bc8135d1e28f8dfd6eb893fdd6ee8b314045492971adc95aa1","bytes":2318,"lines":68},{"path":"tests/test_document_record_idempotency_postgres.sh","sha256":"db078f2d5876e844d455915d3446dc8c25ca8ba95cac87481c26911f839e246d","bytes":17324,"lines":413},{"path":"tests/test_document_record_idempotency_tenant_context_postgres.sh","sha256":"171f35d16891ac58d67a54504ce051a4bff0fedbf7ab43bed2f9ef3f3765b945","bytes":5830,"lines":140},{"path":"tests/test_document_record_persistence_postgres.sh","sha256":"f3d5fcb83a406ac202986f0272f673a8f3c6349752119d27b4d42e4c6bbe7072","bytes":17015,"lines":376},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"6c39b7e25ed127b34a74532ae5e607943b1f73b964bc931f256dc99df75f6b54","bytes":28431,"lines":655}]} From 1b714de0050276832e5e656a9065e65b89580b4a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 21:08:36 +0900 Subject: [PATCH 62/68] docs(document-records): register durable persistence receipts --- docs/DATA_MODEL.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/DATA_MODEL.md b/docs/DATA_MODEL.md index 7d4afd563..c3d4ac891 100644 --- a/docs/DATA_MODEL.md +++ b/docs/DATA_MODEL.md @@ -19,6 +19,8 @@ | `candidate_worker_link` | Legacy append-only candidate-to-worker linkage retained for historical reads; new writes use `candidate_worker_conversion_record`. | | `candidate_worker_conversion_record` | Governed bitemporal candidate-to-worker conversion bound to the hire decision, person, employment, immutable audit event, and outbox evidence. | | `people_mutation_idempotency_record` | Append-only tenant/route/idempotency-key binding to the canonical command digest and first committed created-record identity for governed People writes. | +| `document_record` | Immutable, tenant-scoped document metadata/evidence snapshot with opaque Person/Employment, artifact, audit, and outbox references; it never stores raw document bytes or free-form HR content. | +| `document_record_persist_receipt` | Append-only tenant-scoped idempotency receipt binding one persistence key and semantic-command digest to the first committed `document_record` identity, receipt digest, and database-owned recorded time. | | `criterion_blueprint` | Job-related performance criterion definition. | | `criterion_observation` | Observed criterion result. | | `decision_evidence_set` | Versioned evidence-set header whose database-computed digest and membership are sealed by one accountable selection decision. | @@ -66,6 +68,16 @@ New predictive-validity membership uses `validity_study_case_record` rather than The owning write port acquires an exact-key transaction-scoped advisory lock and writes the HRIS fact, immutable audit/outbox evidence, and idempotency row inside one PostgreSQL transaction. A rolled-back mutation therefore cannot leave a false replay marker. The relation is append-only, TRUNCATE-protected, tenant-RLS isolated, and uses opaque operational UUIDs. The idempotency key is transport correlation, not HR data or authorization evidence; actor, purpose, human-confirmation and resource authorization remain independently required. +## Document-record persistence and idempotency + +`document_record` is the immutable document-metadata system of record for the `document_records` bounded context. It stores only governed metadata and evidence: tenant identity, opaque Person/Employment references, category, uploader/persisting actor references, artifact/source/retention/evidence/application SHA-256 digests, the exact bounded canonical evidence JSON, opaque audit/outbox correlations, business `received_at`, and PostgreSQL-owned `recorded_at`. Raw document bytes, title/free-form HR text, compensation, ratings, credentials, and employment-decision output are excluded from this relation. + +`document_record_persist_receipt` is the append-only uncertain-retry authority for that persistence command. Its tenant-qualified business key is `(tenant_record_id, idempotency_key)`. The row binds the opaque idempotency key to the canonical semantic-command digest, the first committed `document_record_id`, the immutable document/audit/outbox references returned by that command, a receipt digest, and the database-owned `recorded_at`. The receipt does not duplicate document bytes, Person/Employment attributes, or other free-form HR content. + +First execution writes one `document_record` and one receipt in the same PostgreSQL transaction. A same-key/same-semantic retry returns the original committed identity and original database-owned recorded time without creating another document fact. Reusing the key for a different semantic command fails closed before another document write. Concurrent first attempts serialize on a tenant-qualified transaction-scoped advisory lock. The owner function requires exact tenant context before acquiring that coordination state, runs only under `READ COMMITTED`, canonicalizes digest timestamps in UTC, and is exposed to the application executor as an EXECUTE-only `SECURITY DEFINER` capability; the executor has no direct document/receipt table DML authority. + +Both relations are tenant-scoped with forced row-level security and tenant-qualified referential integrity. Receipt UPDATE, DELETE, and TRUNCATE are rejected. This receipt proves creation/retry convergence only; return/destruction completion and recovery-aware deletion remain separate `document_records` lifecycle authority and must not be inferred from a persistence receipt. + ## Audit and outbox normalization `audit_event_record`, `outbox_delivery_record`, and `outbox_delivery_escalation_record` are deliberately separate relations. The audit relation stores the immutable, PII-minimized canonical CloudEvents representation and its SHA-256 digest. The database allowlists the event shape, verifies event and tenant identifiers, requires accountable human confirmation when `data.high_impact` is true, and recomputes the digest over the exact stored UTF-8 text before accepting the row. From 792a9060807cf225eeec4e7a9d90346a978ec64f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 21:09:05 +0900 Subject: [PATCH 63/68] docs(document-records): model persistence receipt cardinality --- docs/ERD.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/ERD.md b/docs/ERD.md index a547cc3a0..bfbc3654d 100644 --- a/docs/ERD.md +++ b/docs/ERD.md @@ -1,12 +1,14 @@ # ERD -For readability, the diagram renders representative `tenant_record` scoping edges rather than repeating the same edge for every tenant-owned relation. The authoritative tenant-isolation contract is `docs/DATA_MODEL.md`: **every owned HRIS fact** stores `tenant_record_id`, every cross-table reference is tenant-qualified, and forced row-level security applies independently to every tenant-scoped table. This omission is visual only; it does not weaken the relational or authorization contract for employment, candidate, evidence, decision, validation-link, compensation, transition, audit, outbox, or outbox-escalation entities. +For readability, the diagram renders representative `tenant_record` scoping edges rather than repeating the same edge for every tenant-owned relation. The authoritative tenant-isolation contract is `docs/DATA_MODEL.md`: **every owned HRIS fact** stores `tenant_record_id`, every cross-table reference is tenant-qualified, and forced row-level security applies independently to every tenant-scoped table. This omission is visual only; it does not weaken the relational or authorization contract for employment, candidate, document, evidence, decision, validation-link, compensation, transition, audit, outbox, or outbox-escalation entities. ```mermaid erDiagram tenant_record ||--o{ person_record : scopes tenant_record ||--o{ organization_unit : scopes tenant_record ||--o{ job_profile : scopes + tenant_record ||--o{ document_record : scopes + tenant_record ||--o{ document_record_persist_receipt : scopes tenant_record ||--o{ audit_event_record : scopes tenant_record ||--o{ outbox_delivery_escalation_record : scopes person_record ||--o{ person_name_record : has_names @@ -23,6 +25,7 @@ erDiagram position_record ||--o{ assignment_record : assigned_through candidate_profile ||--o| candidate_worker_link : may_become person_record ||--o{ candidate_worker_link : links_worker + document_record ||--|| document_record_persist_receipt : first_commit_receipt job_profile ||--o{ criterion_blueprint : requires performance_cycle ||--o{ criterion_observation : schedules criterion_blueprint ||--o{ criterion_observation : produces @@ -53,6 +56,8 @@ Every owned HRIS fact carries `tenant_record_id`. Relationships that cross table A candidate profile can be linked to at most one worker identity within its tenant. A person identity can have multiple candidate-worker links across reapplications or historical candidate profiles, so the person-side cardinality is one-to-many. +A `document_record` is one immutable document-metadata fact owned by `document_records`. It deliberately keeps Person, Employment, artifact, audit, and outbox correlations as opaque owner-contract references rather than cross-context foreign keys. One first committed document persistence command creates exactly one `document_record_persist_receipt`: the receipt has tenant-qualified uniqueness on both `(tenant_record_id, idempotency_key)` and `(tenant_record_id, document_record_id)`, and the latter pair is a foreign key to the document fact. The receipt stores only replay identity/digests and database-owned time; it does not duplicate document bytes or free-form HR data. + Each criterion observation belongs to one effective-dated performance cycle so reporting periods remain reconstructable across effective and system time. A high-impact selection decision seals exactly one versioned `decision_evidence_set`. Evidence members are inserted while the set is open; the decision records the set reference and atomically changes that set to sealed. After sealing, neither new evidence members nor a second decision may reuse that evidence set. This prevents post-decision evidence drift while retaining normalized, version-addressable evidence. From ce7a5807fcf109fe2588d94f3eeef62f18f995f0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 21:11:38 +0900 Subject: [PATCH 64/68] fix(document-records): inventory persistence receipt --- scripts/foundation-contract-core.mjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/foundation-contract-core.mjs b/scripts/foundation-contract-core.mjs index a9b56d8de..da71ee93f 100644 --- a/scripts/foundation-contract-core.mjs +++ b/scripts/foundation-contract-core.mjs @@ -142,7 +142,7 @@ export const DATABASE_OBJECT_NAMES = Object.freeze([ 'compensation_decision', 'validation_study', 'study_population_snapshot', 'study_predictor_link', 'study_criterion_link', 'analysis_manifest', 'analysis_artifact', 'policy_recommendation', 'policy_review_decision', - 'document_record', 'document_version', 'document_segment', 'image_artifact', + 'document_record', 'document_record_persist_receipt', 'document_version', 'document_segment', 'image_artifact', 'evidence_record', 'evidence_source_segment', 'authorization_policy', 'authorization_decision', 'audit_event', 'audit_event_record', 'data_rights_request', 'outbox_event', 'outbox_delivery_record', 'outbox_delivery_escalation_record', @@ -156,7 +156,8 @@ export const MIGRATION_BACKED_DATABASE_OBJECT_NAMES = Object.freeze([ 'job_analysis_task_item', 'job_analysis_ksao_item', 'job_analysis_task_ksao_link', - 'job_analysis_write_command' + 'job_analysis_write_command', + 'document_record_persist_receipt' ]); const UNFINISHED_MARKER_LINE_PATTERN = /^\s*(?:#{1,6}\s+|[-*+]\s+)?(?:\[(?:TODO|TBD|FIXME)\]|\{\{(?:TODO|TBD|FIXME)\}\}|<(?:TODO|TBD|FIXME)>|(?:TODO|TBD|FIXME)(?:\s*:\s*.*)?\s*)$/i; @@ -703,4 +704,4 @@ export function runCli(rootPath, outputStream = process.stdout, errorStream = pr } errorStream.write(`${JSON.stringify({ status: 'failed', error_count: errors.length, errors }, null, 2)}\n`); return 1; -} \ No newline at end of file +} From f8d035c2e65401149612023a18d99bba370ea425 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 21:12:32 +0900 Subject: [PATCH 65/68] test(document-records): prove receipt migration inventory --- tests/foundation-contract.test.mjs | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/foundation-contract.test.mjs b/tests/foundation-contract.test.mjs index c2f8f7589..139e62b19 100644 --- a/tests/foundation-contract.test.mjs +++ b/tests/foundation-contract.test.mjs @@ -53,6 +53,11 @@ function writeMigrationBackedTables(root) { 'CREATE TABLE job_analysis_write_command (tenant_record_id uuid NOT NULL);' ].join('\n') + '\n' ); + write( + root, + 'database/migrations/0024_document_record_idempotent_persistence.sql', + 'CREATE TABLE document_record_persist_receipt (tenant_record_id uuid NOT NULL);\n' + ); } function makeMinimalValidFoundation(root) { @@ -129,6 +134,8 @@ test('required constants are frozen and use accepted values', () => { assert.ok(REQUIRED_FILES.length > 20); assert.ok(DATABASE_OBJECT_NAMES.every(isValidDatabaseObjectName)); assert.ok(DATABASE_OBJECT_NAMES.includes('people_mutation_idempotency_record')); + assert.ok(DATABASE_OBJECT_NAMES.includes('document_record_persist_receipt')); + assert.ok(MIGRATION_BACKED_DATABASE_OBJECT_NAMES.includes('document_record_persist_receipt')); assert.ok(MATURITY_VALUES.has('accepted_architecture')); }); @@ -182,6 +189,32 @@ test('migration-backed validation ignores fake CREATE TABLE text in comments and } }); +test('document receipt migration identity requires executable CREATE TABLE evidence', () => { + const root = temporaryDirectory(); + try { + write( + root, + 'database/migrations/0012_people_mutation_idempotency.sql', + 'CREATE TABLE people_mutation_idempotency_record (tenant_record_id uuid NOT NULL);\n' + ); + writeMigrationBackedTables(root); + write( + root, + 'database/migrations/0024_document_record_idempotent_persistence.sql', + [ + '-- CREATE TABLE document_record_persist_receipt (tenant_record_id uuid);', + "SELECT 'CREATE TABLE document_record_persist_receipt (tenant_record_id uuid);';", + 'SELECT $receipt$CREATE TABLE document_record_persist_receipt (tenant_record_id uuid);$receipt$;' + ].join('\n') + ); + assert.deepEqual(validateMigrationBackedDatabaseObjectNames(root), [ + 'Migration-backed database object is missing from migrations: document_record_persist_receipt' + ]); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + test('collectMarkdownFiles handles missing directories and stable recursion', () => { const root = temporaryDirectory(); try { From 1ba3a15ef3e0c9f60e3b5f36526271382610cc95 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 21:16:51 +0900 Subject: [PATCH 66/68] fix(document-records): reseal receipt inventory manifest --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 737874e2e..65d86be71 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"b6a4365936b66803a8112f034c77d53d33301a7a798ed4f68746a4f2d8b081d7","bytes":6651,"lines":125},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"321c43f388dd561b8867684daac74b0c21f3676d66e15f941feed28d5cf02459","bytes":17829,"lines":78},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"database/migrations/0021_document_record_persistence.sql","sha256":"e2dd9ca0c17141c2b3e06f64726f3ac0fa7cb1cd02798564cef72a12455a1286","bytes":14001,"lines":334},{"path":"database/migrations/0022_document_record_evidence_unique_keys.sql","sha256":"716960d224f1d2feabebc9302db93d7c2dd945978bbc806663eacc27f8606d73","bytes":631,"lines":15},{"path":"database/migrations/0023_document_record_canonical_encoding.sql","sha256":"9600bd071c39d904c041cf8037add92e582af577b2edc0de0f39801095dca195","bytes":4364,"lines":87},{"path":"database/migrations/0024_document_record_idempotent_persistence.sql","sha256":"31b25665cd2a4be256d0fa3e392fdbfc7e7f87c51721dfa4f5d0c6bec2dbbbaa","bytes":17124,"lines":394},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"8db14e56a3c358b8ce97532862d70a440d9141459e6e4e81fa587a998d684d22","bytes":15891,"lines":87},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"9b165006893930266b3af598468219931bfd6fa2f52896ba4d2ffb55bcb2e4b0","bytes":11986,"lines":65},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"8b24cc0af5b5f5040cfd0052ec9f27452e9afb47969d5b717a81b5bcac466fe3","bytes":20040,"lines":140},{"path":"docs/THREAT_MODEL.md","sha256":"95a2367829820a1c7732e292adb89a38be55452f60e7bf3c6caa347450de6ec0","bytes":8595,"lines":25},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"dbe96dfd47066288cec835789de54cc4293f920d2ad4b0e0dba930191d7d249b","bytes":5551,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"c7bfbda34996f717ed31f8307acc16a5d69ae464edb184ab5c8ec4b2d5763cbc","bytes":5958,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/0107-document-record-persistence.md","sha256":"04dcfa124d8cfcb2dc1527fbafaf82a217a0471f5aa27392e52ce5dfcea54810","bytes":6448,"lines":48},{"path":"docs/adr/0309-document-record-idempotent-persistence.md","sha256":"9e0bf26886b246956b2bcb540ff5d9e4e0d5e480d578c0f6c6d0951cb0415ec0","bytes":16322,"lines":104},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/document-record-persistence-references.md","sha256":"e371afc5b15351682b66477e48654554704a62fa9db5fd134767d47ce8d88b36","bytes":2038,"lines":23},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"docs/traceability/document-record-idempotent-persistence.md","sha256":"c585a24a744a41ca75da0f9e2e14ad354841bd24bad9e06660005bd4564767bf","bytes":13411,"lines":66},{"path":"docs/traceability/document-record-persistence.md","sha256":"3849ab6642f8b171aae6b379781fb19028635996428fbc37788334bd1feb167a","bytes":4120,"lines":30},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"aa1965c7101551570a64c9e679c2f9042eb449173fa82face5e94e9c72ded8d1","bytes":29278,"lines":706},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/document_record_idempotency_postcommit_recovery_companion.sh","sha256":"ecbde2131fc5341dc11017305d0bb114136dd1289a05ece14e7c3080a8a52e93","bytes":11193,"lines":277},{"path":"tests/foundation-contract.test.mjs","sha256":"648533b4aff8cee643df4afc06b463eda788e002e11d043971c8a16804c68501","bytes":14943,"lines":387},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_document_record_canonical_bytes_postgres.sh","sha256":"10b12f2f7afeb04fc04182170d2d00bca69b4f0f51c604ee565308b906d8c550","bytes":8394,"lines":177},{"path":"tests/test_document_record_evidence_unique_keys_postgres.sh","sha256":"d678b4ee7f1c69c16632f188ae8f7b5c717ecc55e49dc09d7889ebe02ed7212a","bytes":5058,"lines":103},{"path":"tests/test_document_record_idempotency_function_acl_postgres.sh","sha256":"81b0587ea27101bc57553de9c7da09e8cccb93807dff1128d17df263085e9714","bytes":10268,"lines":247},{"path":"tests/test_document_record_idempotency_isolation_postgres.sh","sha256":"a8e7a099e9b046753591167ddfe4e4206bccd2f00f182925720fa3c15b880a32","bytes":1873,"lines":62},{"path":"tests/test_document_record_idempotency_postcommit_recovery_contract.py","sha256":"69cea86ef064c7bc8135d1e28f8dfd6eb893fdd6ee8b314045492971adc95aa1","bytes":2318,"lines":68},{"path":"tests/test_document_record_idempotency_postgres.sh","sha256":"db078f2d5876e844d455915d3446dc8c25ca8ba95cac87481c26911f839e246d","bytes":17324,"lines":413},{"path":"tests/test_document_record_idempotency_tenant_context_postgres.sh","sha256":"171f35d16891ac58d67a54504ce051a4bff0fedbf7ab43bed2f9ef3f3765b945","bytes":5830,"lines":140},{"path":"tests/test_document_record_persistence_postgres.sh","sha256":"f3d5fcb83a406ac202986f0272f673a8f3c6349752119d27b4d42e4c6bbe7072","bytes":17015,"lines":376},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"6c39b7e25ed127b34a74532ae5e607943b1f73b964bc931f256dc99df75f6b54","bytes":28431,"lines":655}]} +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"b6a4365936b66803a8112f034c77d53d33301a7a798ed4f68746a4f2d8b081d7","bytes":6651,"lines":125},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"321c43f388dd561b8867684daac74b0c21f3676d66e15f941feed28d5cf02459","bytes":17829,"lines":78},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"database/migrations/0021_document_record_persistence.sql","sha256":"e2dd9ca0c17141c2b3e06f64726f3ac0fa7cb1cd02798564cef72a12455a1286","bytes":14001,"lines":334},{"path":"database/migrations/0022_document_record_evidence_unique_keys.sql","sha256":"716960d224f1d2feabebc9302db93d7c2dd945978bbc806663eacc27f8606d73","bytes":631,"lines":15},{"path":"database/migrations/0023_document_record_canonical_encoding.sql","sha256":"9600bd071c39d904c041cf8037add92e582af577b2edc0de0f39801095dca195","bytes":4364,"lines":87},{"path":"database/migrations/0024_document_record_idempotent_persistence.sql","sha256":"31b25665cd2a4be256d0fa3e392fdbfc7e7f87c51721dfa4f5d0c6bec2dbbbaa","bytes":17124,"lines":394},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"0528c309dd9ce998f71d91a474f6106ee49f0328fb9c9697b81f8ebe5e9439f5","bytes":16169,"lines":97},{"path":"docs/ERD.md","sha256":"a8b76d2dd0499e59ba5aedbaadf00437ef37a31ec64268ccb0dfbc1c90c09e6f","bytes":7852,"lines":75},{"path":"docs/OPERABILITY.md","sha256":"8db14e56a3c358b8ce97532862d70a440d9141459e6e4e81fa587a998d684d22","bytes":15891,"lines":87},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"73e052e80ec3e8b4669d31dd1699eea54cc5325cddf0db16c90ba5d8e247a93a","bytes":11980,"lines":65},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"8b24cc0af5b5f5040cfd0052ec9f27452e9afb47969d5b717a81b5bcac466fe3","bytes":20040,"lines":140},{"path":"docs/THREAT_MODEL.md","sha256":"95a2367829820a1c7732e292adb89a38be55452f60e7bf3c6caa347450de6ec0","bytes":8595,"lines":25},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"dbe96dfd47066288cec835789de54cc4293f920d2ad4b0e0dba930191d7d249b","bytes":5551,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"c7bfbda34996f717ed31f8307acc16a5d69ae464edb184ab5c8ec4b2d5763cbc","bytes":5958,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/0107-document-record-persistence.md","sha256":"04dcfa124d8cfcb2dc1527fbafaf82a217a0471f5aa27392e52ce5dfcea54810","bytes":6448,"lines":48},{"path":"docs/adr/0309-document-record-idempotent-persistence.md","sha256":"9e0bf26886b246956b2bcb540ff5d9e4e0d5e480d578c0f6c6d0951cb0415ec0","bytes":16322,"lines":104},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/document-record-persistence-references.md","sha256":"e371afc5b15351682b66477e48654554704a62fa9db5fd134767d47ce8d88b36","bytes":2038,"lines":23},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"docs/traceability/document-record-idempotent-persistence.md","sha256":"c585a24a744a41ca75da0f9e2e14ad354841bd24bad9e06660005bd4564767bf","bytes":13411,"lines":66},{"path":"docs/traceability/document-record-persistence.md","sha256":"3849ab6642f8b171aae6b379781fb19028635996428fbc37788334bd1feb167a","bytes":4120,"lines":30},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"7ca3b9d37d9fe168ece9866d5af8ffbc01dd8c1f91a96a42d26b89c909ffce46","bytes":29351,"lines":707},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/document_record_idempotency_postcommit_recovery_companion.sh","sha256":"ecbde2131fc5341dc11017305d0bb114136dd1289a05ece14e7c3080a8a52e93","bytes":11193,"lines":277},{"path":"tests/foundation-contract.test.mjs","sha256":"ecffcf694c2388b55d665bb94e1112dcdaf4cc3266cc4e3f928a1cae60cb944b","bytes":16343,"lines":420},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_document_record_canonical_bytes_postgres.sh","sha256":"10b12f2f7afeb04fc04182170d2d00bca69b4f0f51c604ee565308b906d8c550","bytes":8394,"lines":177},{"path":"tests/test_document_record_evidence_unique_keys_postgres.sh","sha256":"d678b4ee7f1c69c16632f188ae8f7b5c717ecc55e49dc09d7889ebe02ed7212a","bytes":5058,"lines":103},{"path":"tests/test_document_record_idempotency_function_acl_postgres.sh","sha256":"81b0587ea27101bc57553de9c7da09e8cccb93807dff1128d17df263085e9714","bytes":10268,"lines":247},{"path":"tests/test_document_record_idempotency_isolation_postgres.sh","sha256":"a8e7a099e9b046753591167ddfe4e4206bccd2f00f182925720fa3c15b880a32","bytes":1873,"lines":62},{"path":"tests/test_document_record_idempotency_postcommit_recovery_contract.py","sha256":"69cea86ef064c7bc8135d1e28f8dfd6eb893fdd6ee8b314045492971adc95aa1","bytes":2318,"lines":68},{"path":"tests/test_document_record_idempotency_postgres.sh","sha256":"db078f2d5876e844d455915d3446dc8c25ca8ba95cac87481c26911f839e246d","bytes":17324,"lines":413},{"path":"tests/test_document_record_idempotency_tenant_context_postgres.sh","sha256":"171f35d16891ac58d67a54504ce051a4bff0fedbf7ab43bed2f9ef3f3765b945","bytes":5830,"lines":140},{"path":"tests/test_document_record_persistence_postgres.sh","sha256":"f3d5fcb83a406ac202986f0272f673a8f3c6349752119d27b4d42e4c6bbe7072","bytes":17015,"lines":376},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"6c39b7e25ed127b34a74532ae5e607943b1f73b964bc931f256dc99df75f6b54","bytes":28431,"lines":655}]} From 0bdf9012d4f46e52b482694e6c0272b38d18d816 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 21:20:33 +0900 Subject: [PATCH 67/68] fix(document-records): model optional legacy receipt cardinality --- docs/ERD.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ERD.md b/docs/ERD.md index bfbc3654d..8bd9c0bb8 100644 --- a/docs/ERD.md +++ b/docs/ERD.md @@ -25,7 +25,7 @@ erDiagram position_record ||--o{ assignment_record : assigned_through candidate_profile ||--o| candidate_worker_link : may_become person_record ||--o{ candidate_worker_link : links_worker - document_record ||--|| document_record_persist_receipt : first_commit_receipt + document_record ||--o| document_record_persist_receipt : may_have_first_commit_receipt job_profile ||--o{ criterion_blueprint : requires performance_cycle ||--o{ criterion_observation : schedules criterion_blueprint ||--o{ criterion_observation : produces @@ -56,7 +56,7 @@ Every owned HRIS fact carries `tenant_record_id`. Relationships that cross table A candidate profile can be linked to at most one worker identity within its tenant. A person identity can have multiple candidate-worker links across reapplications or historical candidate profiles, so the person-side cardinality is one-to-many. -A `document_record` is one immutable document-metadata fact owned by `document_records`. It deliberately keeps Person, Employment, artifact, audit, and outbox correlations as opaque owner-contract references rather than cross-context foreign keys. One first committed document persistence command creates exactly one `document_record_persist_receipt`: the receipt has tenant-qualified uniqueness on both `(tenant_record_id, idempotency_key)` and `(tenant_record_id, document_record_id)`, and the latter pair is a foreign key to the document fact. The receipt stores only replay identity/digests and database-owned time; it does not duplicate document bytes or free-form HR data. +A `document_record` is one immutable document-metadata fact owned by `document_records`. It deliberately keeps Person, Employment, artifact, audit, and outbox correlations as opaque owner-contract references rather than cross-context foreign keys. A persistence command executed through `persist_document_record_once(...)` creates exactly one `document_record_persist_receipt` with its first committed document fact. The receipt has tenant-qualified uniqueness on both `(tenant_record_id, idempotency_key)` and `(tenant_record_id, document_record_id)`, and the latter pair is a foreign key to the document fact, so each receipt identifies exactly one document and each document can have at most one receipt. Migration 0024 does not backfill or require receipts for document rows that already existed before the idempotent write port, so the dataset-level document-to-receipt cardinality is zero-or-one. The receipt stores only replay identity/digests and database-owned time; it does not duplicate document bytes or free-form HR data. Each criterion observation belongs to one effective-dated performance cycle so reporting periods remain reconstructable across effective and system time. From b996e19090e5cc38ef0a4d41a8b4de6d561b89d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 21:22:20 +0900 Subject: [PATCH 68/68] fix(document-records): reseal corrected receipt cardinality --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 65d86be71..cc4fafb9e 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"b6a4365936b66803a8112f034c77d53d33301a7a798ed4f68746a4f2d8b081d7","bytes":6651,"lines":125},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"321c43f388dd561b8867684daac74b0c21f3676d66e15f941feed28d5cf02459","bytes":17829,"lines":78},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"database/migrations/0021_document_record_persistence.sql","sha256":"e2dd9ca0c17141c2b3e06f64726f3ac0fa7cb1cd02798564cef72a12455a1286","bytes":14001,"lines":334},{"path":"database/migrations/0022_document_record_evidence_unique_keys.sql","sha256":"716960d224f1d2feabebc9302db93d7c2dd945978bbc806663eacc27f8606d73","bytes":631,"lines":15},{"path":"database/migrations/0023_document_record_canonical_encoding.sql","sha256":"9600bd071c39d904c041cf8037add92e582af577b2edc0de0f39801095dca195","bytes":4364,"lines":87},{"path":"database/migrations/0024_document_record_idempotent_persistence.sql","sha256":"31b25665cd2a4be256d0fa3e392fdbfc7e7f87c51721dfa4f5d0c6bec2dbbbaa","bytes":17124,"lines":394},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"0528c309dd9ce998f71d91a474f6106ee49f0328fb9c9697b81f8ebe5e9439f5","bytes":16169,"lines":97},{"path":"docs/ERD.md","sha256":"a8b76d2dd0499e59ba5aedbaadf00437ef37a31ec64268ccb0dfbc1c90c09e6f","bytes":7852,"lines":75},{"path":"docs/OPERABILITY.md","sha256":"8db14e56a3c358b8ce97532862d70a440d9141459e6e4e81fa587a998d684d22","bytes":15891,"lines":87},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"73e052e80ec3e8b4669d31dd1699eea54cc5325cddf0db16c90ba5d8e247a93a","bytes":11980,"lines":65},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"8b24cc0af5b5f5040cfd0052ec9f27452e9afb47969d5b717a81b5bcac466fe3","bytes":20040,"lines":140},{"path":"docs/THREAT_MODEL.md","sha256":"95a2367829820a1c7732e292adb89a38be55452f60e7bf3c6caa347450de6ec0","bytes":8595,"lines":25},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"dbe96dfd47066288cec835789de54cc4293f920d2ad4b0e0dba930191d7d249b","bytes":5551,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"c7bfbda34996f717ed31f8307acc16a5d69ae464edb184ab5c8ec4b2d5763cbc","bytes":5958,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/0107-document-record-persistence.md","sha256":"04dcfa124d8cfcb2dc1527fbafaf82a217a0471f5aa27392e52ce5dfcea54810","bytes":6448,"lines":48},{"path":"docs/adr/0309-document-record-idempotent-persistence.md","sha256":"9e0bf26886b246956b2bcb540ff5d9e4e0d5e480d578c0f6c6d0951cb0415ec0","bytes":16322,"lines":104},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/document-record-persistence-references.md","sha256":"e371afc5b15351682b66477e48654554704a62fa9db5fd134767d47ce8d88b36","bytes":2038,"lines":23},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"docs/traceability/document-record-idempotent-persistence.md","sha256":"c585a24a744a41ca75da0f9e2e14ad354841bd24bad9e06660005bd4564767bf","bytes":13411,"lines":66},{"path":"docs/traceability/document-record-persistence.md","sha256":"3849ab6642f8b171aae6b379781fb19028635996428fbc37788334bd1feb167a","bytes":4120,"lines":30},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"7ca3b9d37d9fe168ece9866d5af8ffbc01dd8c1f91a96a42d26b89c909ffce46","bytes":29351,"lines":707},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/document_record_idempotency_postcommit_recovery_companion.sh","sha256":"ecbde2131fc5341dc11017305d0bb114136dd1289a05ece14e7c3080a8a52e93","bytes":11193,"lines":277},{"path":"tests/foundation-contract.test.mjs","sha256":"ecffcf694c2388b55d665bb94e1112dcdaf4cc3266cc4e3f928a1cae60cb944b","bytes":16343,"lines":420},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_document_record_canonical_bytes_postgres.sh","sha256":"10b12f2f7afeb04fc04182170d2d00bca69b4f0f51c604ee565308b906d8c550","bytes":8394,"lines":177},{"path":"tests/test_document_record_evidence_unique_keys_postgres.sh","sha256":"d678b4ee7f1c69c16632f188ae8f7b5c717ecc55e49dc09d7889ebe02ed7212a","bytes":5058,"lines":103},{"path":"tests/test_document_record_idempotency_function_acl_postgres.sh","sha256":"81b0587ea27101bc57553de9c7da09e8cccb93807dff1128d17df263085e9714","bytes":10268,"lines":247},{"path":"tests/test_document_record_idempotency_isolation_postgres.sh","sha256":"a8e7a099e9b046753591167ddfe4e4206bccd2f00f182925720fa3c15b880a32","bytes":1873,"lines":62},{"path":"tests/test_document_record_idempotency_postcommit_recovery_contract.py","sha256":"69cea86ef064c7bc8135d1e28f8dfd6eb893fdd6ee8b314045492971adc95aa1","bytes":2318,"lines":68},{"path":"tests/test_document_record_idempotency_postgres.sh","sha256":"db078f2d5876e844d455915d3446dc8c25ca8ba95cac87481c26911f839e246d","bytes":17324,"lines":413},{"path":"tests/test_document_record_idempotency_tenant_context_postgres.sh","sha256":"171f35d16891ac58d67a54504ce051a4bff0fedbf7ab43bed2f9ef3f3765b945","bytes":5830,"lines":140},{"path":"tests/test_document_record_persistence_postgres.sh","sha256":"f3d5fcb83a406ac202986f0272f673a8f3c6349752119d27b4d42e4c6bbe7072","bytes":17015,"lines":376},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"6c39b7e25ed127b34a74532ae5e607943b1f73b964bc931f256dc99df75f6b54","bytes":28431,"lines":655}]} +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"b6a4365936b66803a8112f034c77d53d33301a7a798ed4f68746a4f2d8b081d7","bytes":6651,"lines":125},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"321c43f388dd561b8867684daac74b0c21f3676d66e15f941feed28d5cf02459","bytes":17829,"lines":78},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"database/migrations/0021_document_record_persistence.sql","sha256":"e2dd9ca0c17141c2b3e06f64726f3ac0fa7cb1cd02798564cef72a12455a1286","bytes":14001,"lines":334},{"path":"database/migrations/0022_document_record_evidence_unique_keys.sql","sha256":"716960d224f1d2feabebc9302db93d7c2dd945978bbc806663eacc27f8606d73","bytes":631,"lines":15},{"path":"database/migrations/0023_document_record_canonical_encoding.sql","sha256":"9600bd071c39d904c041cf8037add92e582af577b2edc0de0f39801095dca195","bytes":4364,"lines":87},{"path":"database/migrations/0024_document_record_idempotent_persistence.sql","sha256":"31b25665cd2a4be256d0fa3e392fdbfc7e7f87c51721dfa4f5d0c6bec2dbbbaa","bytes":17124,"lines":394},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"0528c309dd9ce998f71d91a474f6106ee49f0328fb9c9697b81f8ebe5e9439f5","bytes":16169,"lines":97},{"path":"docs/ERD.md","sha256":"2fef217f1b7789685dcd7cc37fef25782c2bb48268b5e39001293a9cbb52f755","bytes":8217,"lines":75},{"path":"docs/OPERABILITY.md","sha256":"8db14e56a3c358b8ce97532862d70a440d9141459e6e4e81fa587a998d684d22","bytes":15891,"lines":87},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"73e052e80ec3e8b4669d31dd1699eea54cc5325cddf0db16c90ba5d8e247a93a","bytes":11980,"lines":65},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"8b24cc0af5b5f5040cfd0052ec9f27452e9afb47969d5b717a81b5bcac466fe3","bytes":20040,"lines":140},{"path":"docs/THREAT_MODEL.md","sha256":"95a2367829820a1c7732e292adb89a38be55452f60e7bf3c6caa347450de6ec0","bytes":8595,"lines":25},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"dbe96dfd47066288cec835789de54cc4293f920d2ad4b0e0dba930191d7d249b","bytes":5551,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"c7bfbda34996f717ed31f8307acc16a5d69ae464edb184ab5c8ec4b2d5763cbc","bytes":5958,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/0107-document-record-persistence.md","sha256":"04dcfa124d8cfcb2dc1527fbafaf82a217a0471f5aa27392e52ce5dfcea54810","bytes":6448,"lines":48},{"path":"docs/adr/0309-document-record-idempotent-persistence.md","sha256":"9e0bf26886b246956b2bcb540ff5d9e4e0d5e480d578c0f6c6d0951cb0415ec0","bytes":16322,"lines":104},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/document-record-persistence-references.md","sha256":"e371afc5b15351682b66477e48654554704a62fa9db5fd134767d47ce8d88b36","bytes":2038,"lines":23},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"docs/traceability/document-record-idempotent-persistence.md","sha256":"c585a24a744a41ca75da0f9e2e14ad354841bd24bad9e06660005bd4564767bf","bytes":13411,"lines":66},{"path":"docs/traceability/document-record-persistence.md","sha256":"3849ab6642f8b171aae6b379781fb19028635996428fbc37788334bd1feb167a","bytes":4120,"lines":30},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"7ca3b9d37d9fe168ece9866d5af8ffbc01dd8c1f91a96a42d26b89c909ffce46","bytes":29351,"lines":707},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/document_record_idempotency_postcommit_recovery_companion.sh","sha256":"ecbde2131fc5341dc11017305d0bb114136dd1289a05ece14e7c3080a8a52e93","bytes":11193,"lines":277},{"path":"tests/foundation-contract.test.mjs","sha256":"ecffcf694c2388b55d665bb94e1112dcdaf4cc3266cc4e3f928a1cae60cb944b","bytes":16343,"lines":420},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_document_record_canonical_bytes_postgres.sh","sha256":"10b12f2f7afeb04fc04182170d2d00bca69b4f0f51c604ee565308b906d8c550","bytes":8394,"lines":177},{"path":"tests/test_document_record_evidence_unique_keys_postgres.sh","sha256":"d678b4ee7f1c69c16632f188ae8f7b5c717ecc55e49dc09d7889ebe02ed7212a","bytes":5058,"lines":103},{"path":"tests/test_document_record_idempotency_function_acl_postgres.sh","sha256":"81b0587ea27101bc57553de9c7da09e8cccb93807dff1128d17df263085e9714","bytes":10268,"lines":247},{"path":"tests/test_document_record_idempotency_isolation_postgres.sh","sha256":"a8e7a099e9b046753591167ddfe4e4206bccd2f00f182925720fa3c15b880a32","bytes":1873,"lines":62},{"path":"tests/test_document_record_idempotency_postcommit_recovery_contract.py","sha256":"69cea86ef064c7bc8135d1e28f8dfd6eb893fdd6ee8b314045492971adc95aa1","bytes":2318,"lines":68},{"path":"tests/test_document_record_idempotency_postgres.sh","sha256":"db078f2d5876e844d455915d3446dc8c25ca8ba95cac87481c26911f839e246d","bytes":17324,"lines":413},{"path":"tests/test_document_record_idempotency_tenant_context_postgres.sh","sha256":"171f35d16891ac58d67a54504ce051a4bff0fedbf7ab43bed2f9ef3f3765b945","bytes":5830,"lines":140},{"path":"tests/test_document_record_persistence_postgres.sh","sha256":"f3d5fcb83a406ac202986f0272f673a8f3c6349752119d27b4d42e4c6bbe7072","bytes":17015,"lines":376},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"6c39b7e25ed127b34a74532ae5e607943b1f73b964bc931f256dc99df75f6b54","bytes":28431,"lines":655}]}