-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add governed candidate withdrawal evidence #67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
seonghobae
wants to merge
20
commits into
feat/normalized-candidate-application
Choose a base branch
from
feat/governed-candidate-withdrawal
base: feat/normalized-candidate-application
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
371a0f9
test(candidate-withdrawal): define governed withdrawal evidence boundary
seonghobae a9107d6
test(candidate-withdrawal): wire exact-head PostgreSQL regression
seonghobae c353166
feat(candidate-withdrawal): persist candidate-initiated immutable evi…
seonghobae b93f521
test(candidate-withdrawal): isolate governed happy-path evidence
seonghobae 79c1fa4
test(candidate-withdrawal): prove anti-forgery and tenant isolation
seonghobae fb2005f
test(candidate-withdrawal): run focused security regression
seonghobae 40c0da9
docs(candidate-withdrawal): record governed withdrawal decision
seonghobae ade9da8
docs(candidate-withdrawal): trace authority and audit evidence
seonghobae cf59b30
docs(candidate-withdrawal): record primary-source references
seonghobae 79d1f62
docs(adr): index active decision records and reseal foundation manifest
seonghobae f2750f4
fix(withdrawal): bind audit provenance fields
seonghobae 5ab9416
chore(withdrawal): sync normalized application base
seonghobae ccaabeb
test(withdrawal): align fixture with normalized application
seonghobae 687231a
test(withdrawal): cover provenance artifacts
seonghobae 18bf097
test(withdrawal): remove stale fixture values
seonghobae f0afa82
fix(candidate-withdrawal): migrate audit envelope validator forward
seonghobae b855af9
fix(workflow): trigger withdrawal migration upgrade checks
seonghobae 4674809
Merge remote-tracking branch 'origin/feat/normalized-candidate-applic…
seonghobae 65d8627
docs(adr): align normalized application maturity
seonghobae ee5a56d
fix(withdrawal): own recorded evidence time
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| name: Candidate Withdrawal Quality | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: | ||
| - develop | ||
| - feat/normalized-candidate-application | ||
| paths: | ||
| - "database/migrations/**" | ||
| - "tests/test_candidate_withdrawal_postgres.sh" | ||
| - "tests/test_candidate_withdrawal_security_postgres.sh" | ||
| - "tests/test_candidate_withdrawal_migration_upgrade_postgres.sh" | ||
| - ".github/workflows/candidate-withdrawal-quality.yml" | ||
| - "docs/adr/0027-governed-candidate-withdrawal.md" | ||
| - "docs/traceability/candidate-withdrawal.md" | ||
| - "docs/doctoring/candidate-withdrawal-references.md" | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: candidate-withdrawal-quality-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| postgres_contract: | ||
| name: Candidate withdrawal PostgreSQL contract | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| services: | ||
| postgres: | ||
| image: postgres:16.14@sha256:33f923b05f64ca54ac4401c01126a6b92afe839a0aa0a52bc5aeb5cc958e5f20 | ||
| env: | ||
| POSTGRES_USER: orgmetra | ||
| POSTGRES_PASSWORD: orgmetra | ||
| POSTGRES_DB: orgmetra | ||
| ports: | ||
| - 5432:5432 | ||
| options: >- | ||
| --health-cmd "pg_isready -U orgmetra -d orgmetra" | ||
| --health-interval 5s | ||
| --health-timeout 5s | ||
| --health-retries 10 | ||
| env: | ||
| DATABASE_URL: postgresql://orgmetra:orgmetra@localhost:5432/orgmetra | ||
| steps: | ||
| - name: Checkout exact candidate | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | ||
| persist-credentials: false | ||
| - name: Prove exact candidate checkout | ||
| env: | ||
| ORGMETRA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | ||
| run: test "$(git rev-parse HEAD)" = "$ORGMETRA_EXPECTED_HEAD_SHA" | ||
| - name: Prove test syntax | ||
| run: >- | ||
| bash -n | ||
| tests/test_candidate_withdrawal_postgres.sh | ||
| tests/test_candidate_withdrawal_security_postgres.sh | ||
| tests/test_candidate_withdrawal_migration_upgrade_postgres.sh | ||
| - name: Prove governed candidate withdrawal persistence | ||
| run: bash tests/test_candidate_withdrawal_postgres.sh | ||
| - name: Prove anti-forgery and tenant isolation | ||
| run: bash tests/test_candidate_withdrawal_security_postgres.sh | ||
| - name: Prove validation is read-only | ||
| run: | | ||
| git diff --exit-code | ||
| test -z "$(git status --porcelain)" | ||
|
|
||
| postgres_migration_upgrade_contract: | ||
| name: Candidate withdrawal migration upgrade contract | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| services: | ||
| postgres: | ||
| image: postgres:16.14@sha256:33f923b05f64ca54ac4401c01126a6b92afe839a0aa0a52bc5aeb5cc958e5f20 | ||
| env: | ||
| POSTGRES_USER: orgmetra | ||
| POSTGRES_PASSWORD: orgmetra | ||
| POSTGRES_DB: orgmetra | ||
| ports: | ||
| - 5432:5432 | ||
| options: >- | ||
| --health-cmd "pg_isready -U orgmetra -d orgmetra" | ||
| --health-interval 5s | ||
| --health-timeout 5s | ||
| --health-retries 10 | ||
| env: | ||
| DATABASE_URL: postgresql://orgmetra:orgmetra@localhost:5432/orgmetra | ||
| steps: | ||
| - name: Checkout exact candidate | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | ||
| persist-credentials: false | ||
| - name: Prove exact candidate checkout | ||
| env: | ||
| ORGMETRA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | ||
| run: test "$(git rev-parse HEAD)" = "$ORGMETRA_EXPECTED_HEAD_SHA" | ||
| - name: Prove migration upgrade path | ||
| run: bash tests/test_candidate_withdrawal_migration_upgrade_postgres.sh | ||
| - name: Prove validation is read-only | ||
| run: | | ||
| git diff --exit-code | ||
| test -z "$(git status --porcelain)" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
195 changes: 195 additions & 0 deletions
195
database/migrations/0015_candidate_withdrawal_governance.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| -- Govern candidate-initiated application withdrawal without reusing raw workflow | ||
| -- stage codes for a terminal action. The application layer remains responsible | ||
| -- for authenticating the candidate; this persistence boundary requires the | ||
| -- resulting identity-resolution evidence, candidate actor, withdrawal evidence, | ||
| -- and exact immutable audit/outbox correlation to agree before accepting a row. | ||
|
|
||
| BEGIN; | ||
|
|
||
| SET LOCAL search_path = public, pg_catalog; | ||
|
|
||
| CREATE TABLE candidate_withdrawal_record ( | ||
| tenant_record_id uuid NOT NULL REFERENCES public.tenant_record(tenant_record_id), | ||
| candidate_withdrawal_record_id uuid PRIMARY KEY, | ||
| candidate_application_record_id uuid NOT NULL, | ||
| initiating_actor_reference text NOT NULL, | ||
| identity_resolution_reference text NOT NULL, | ||
| identity_resolution_digest text NOT NULL, | ||
| withdrawal_evidence_reference text NOT NULL, | ||
| withdrawal_evidence_digest text NOT NULL, | ||
| evidence_version integer NOT NULL, | ||
| withdrawn_at timestamptz NOT NULL, | ||
| audit_event_record_id uuid NOT NULL, | ||
| recorded_at timestamptz NOT NULL DEFAULT pg_catalog.transaction_timestamp(), | ||
| CONSTRAINT candidate_withdrawal_record_id_operational_check | ||
| CHECK ( | ||
| candidate_withdrawal_record_id <> '00000000-0000-0000-0000-000000000000'::uuid | ||
| AND candidate_withdrawal_record_id <> 'ffffffff-ffff-ffff-ffff-ffffffffffff'::uuid | ||
| ), | ||
| CONSTRAINT candidate_withdrawal_application_tenant_fk | ||
| FOREIGN KEY (tenant_record_id, candidate_application_record_id) | ||
| REFERENCES public.candidate_application_record( | ||
| tenant_record_id, candidate_application_record_id | ||
| ), | ||
| CONSTRAINT candidate_withdrawal_audit_tenant_fk | ||
| FOREIGN KEY (tenant_record_id, audit_event_record_id) | ||
| REFERENCES public.audit_event_record(tenant_record_id, audit_event_record_id), | ||
| CONSTRAINT candidate_withdrawal_actor_reference_check | ||
| CHECK ( | ||
| initiating_actor_reference ~ | ||
| '^candidate:[A-Za-z0-9][A-Za-z0-9._~-]*$' | ||
| ), | ||
| CONSTRAINT candidate_withdrawal_identity_reference_check | ||
| CHECK ( | ||
| identity_resolution_reference ~ | ||
| '^identity_resolution:[A-Za-z0-9][A-Za-z0-9._~-]*$' | ||
| ), | ||
| CONSTRAINT candidate_withdrawal_identity_digest_check | ||
| CHECK (identity_resolution_digest ~ '^[0-9a-f]{64}$'), | ||
| CONSTRAINT candidate_withdrawal_evidence_reference_check | ||
| CHECK ( | ||
| withdrawal_evidence_reference ~ | ||
| '^candidate_withdrawal_evidence:[A-Za-z0-9][A-Za-z0-9._~-]*$' | ||
| ), | ||
| CONSTRAINT candidate_withdrawal_evidence_digest_check | ||
| CHECK (withdrawal_evidence_digest ~ '^[0-9a-f]{64}$'), | ||
| CONSTRAINT candidate_withdrawal_evidence_version_check | ||
| CHECK (evidence_version >= 1 AND evidence_version <= 1000000), | ||
| CONSTRAINT candidate_withdrawal_recorded_order_check | ||
| CHECK (withdrawn_at <= recorded_at), | ||
| CONSTRAINT candidate_withdrawal_tenant_identity_unique | ||
| UNIQUE (tenant_record_id, candidate_withdrawal_record_id), | ||
| CONSTRAINT candidate_withdrawal_application_unique | ||
| UNIQUE (tenant_record_id, candidate_application_record_id), | ||
| CONSTRAINT candidate_withdrawal_audit_identity_unique | ||
| UNIQUE (tenant_record_id, audit_event_record_id) | ||
| ); | ||
|
|
||
| CREATE FUNCTION public.validate_candidate_withdrawal_evidence() | ||
| RETURNS trigger | ||
| LANGUAGE plpgsql | ||
| SET search_path = pg_catalog, public, pg_temp | ||
| AS $$ | ||
| DECLARE | ||
| application_submitted_at timestamptz; | ||
| audit_event_envelope jsonb; | ||
| audit_event_time timestamptz; | ||
| BEGIN | ||
| -- recorded_at is database-owned evidence, not a caller-provided timestamp. | ||
| NEW.recorded_at := pg_catalog.transaction_timestamp(); | ||
|
|
||
| SELECT application_record.submitted_at | ||
| INTO application_submitted_at | ||
| FROM public.candidate_application_record AS application_record | ||
| WHERE application_record.tenant_record_id = NEW.tenant_record_id | ||
| AND application_record.candidate_application_record_id = | ||
| NEW.candidate_application_record_id; | ||
|
|
||
| IF NOT FOUND THEN | ||
| RAISE EXCEPTION 'candidate withdrawal requires a tenant-local application' | ||
| USING ERRCODE = '23503'; | ||
| END IF; | ||
|
|
||
| IF NEW.withdrawn_at < application_submitted_at THEN | ||
| RAISE EXCEPTION 'candidate withdrawal cannot predate application submission' | ||
| USING ERRCODE = '23514'; | ||
| END IF; | ||
|
|
||
| SELECT audit_record.canonical_event_json::jsonb | ||
| INTO audit_event_envelope | ||
| FROM public.audit_event_record AS audit_record | ||
| WHERE audit_record.tenant_record_id = NEW.tenant_record_id | ||
| AND audit_record.audit_event_record_id = NEW.audit_event_record_id; | ||
|
|
||
| IF NOT FOUND THEN | ||
| RAISE EXCEPTION 'candidate withdrawal requires a tenant-local immutable audit event' | ||
| USING ERRCODE = '23503'; | ||
| END IF; | ||
|
|
||
| audit_event_time := (audit_event_envelope ->> 'time')::timestamptz; | ||
|
|
||
| IF audit_event_envelope ->> 'source' <> 'urn:orgmetra:talent_acquisition' | ||
| OR audit_event_envelope ->> 'type' <> 'orgmetra.candidate.application_withdrawn' | ||
| OR audit_event_envelope ->> 'subject' | ||
| <> 'candidate_withdrawal_record:' || NEW.candidate_withdrawal_record_id::text | ||
| OR audit_event_envelope ->> 'orgmetraactor' <> NEW.initiating_actor_reference | ||
| OR audit_event_envelope ->> 'orgmetrapurpose' <> 'candidate_withdrawal' | ||
| OR audit_event_envelope ->> 'orgmetrareason' <> 'candidate_requested' | ||
| OR audit_event_envelope ->> 'orgmetraevidence' <> NEW.withdrawal_evidence_reference | ||
| OR audit_event_envelope #>> '{data,identity_resolution_reference}' | ||
| <> NEW.identity_resolution_reference | ||
| OR audit_event_envelope #>> '{data,identity_resolution_digest}' | ||
| <> NEW.identity_resolution_digest | ||
| OR audit_event_envelope #>> '{data,withdrawal_evidence_digest}' | ||
| <> NEW.withdrawal_evidence_digest | ||
| OR audit_event_envelope #>> '{data,evidence_version}' | ||
| <> NEW.evidence_version::text | ||
| OR audit_event_envelope ? 'orgmetraconfirmation' | ||
| OR (audit_event_envelope #>> '{data,high_impact}')::boolean IS NOT FALSE | ||
| OR audit_event_envelope #>> '{data,result_code}' <> 'application_withdrawn' | ||
| OR audit_event_time <> NEW.withdrawn_at | ||
| OR audit_event_time > NEW.recorded_at THEN | ||
| RAISE EXCEPTION 'candidate withdrawal audit envelope does not bind exact candidate provenance' | ||
| USING ERRCODE = '23514'; | ||
| END IF; | ||
|
seonghobae marked this conversation as resolved.
devin-ai-integration[bot] marked this conversation as resolved.
|
||
|
|
||
| IF NOT EXISTS ( | ||
| SELECT 1 | ||
| FROM public.outbox_delivery_record AS delivery_record | ||
| WHERE delivery_record.tenant_record_id = NEW.tenant_record_id | ||
| AND delivery_record.audit_event_record_id = NEW.audit_event_record_id | ||
| ) THEN | ||
| RAISE EXCEPTION 'candidate withdrawal audit event requires transactional outbox delivery evidence' | ||
| USING ERRCODE = '23514'; | ||
| END IF; | ||
|
|
||
| RETURN NEW; | ||
| END; | ||
| $$; | ||
|
|
||
| CREATE TRIGGER candidate_withdrawal_governance_guard | ||
| BEFORE INSERT ON candidate_withdrawal_record | ||
| FOR EACH ROW | ||
| EXECUTE FUNCTION public.validate_candidate_withdrawal_evidence(); | ||
|
seonghobae marked this conversation as resolved.
|
||
|
|
||
| CREATE FUNCTION public.reject_candidate_withdrawal_mutation() | ||
| RETURNS trigger | ||
| LANGUAGE plpgsql | ||
| SET search_path = pg_catalog, public, pg_temp | ||
| AS $$ | ||
| BEGIN | ||
| RAISE EXCEPTION 'candidate withdrawal evidence is append-only' | ||
| USING ERRCODE = '55000'; | ||
| END; | ||
| $$; | ||
|
|
||
| CREATE TRIGGER candidate_withdrawal_append_only_guard | ||
| BEFORE UPDATE OR DELETE ON candidate_withdrawal_record | ||
| FOR EACH ROW | ||
| EXECUTE FUNCTION public.reject_candidate_withdrawal_mutation(); | ||
|
|
||
| CREATE FUNCTION public.reject_candidate_withdrawal_truncate() | ||
| RETURNS trigger | ||
| LANGUAGE plpgsql | ||
| SET search_path = pg_catalog, public, pg_temp | ||
| AS $$ | ||
| BEGIN | ||
| RAISE EXCEPTION 'candidate withdrawal evidence cannot be truncated' | ||
| USING ERRCODE = '55000'; | ||
| END; | ||
| $$; | ||
|
|
||
| CREATE TRIGGER candidate_withdrawal_truncate_guard | ||
| BEFORE TRUNCATE ON candidate_withdrawal_record | ||
| FOR EACH STATEMENT | ||
| EXECUTE FUNCTION public.reject_candidate_withdrawal_truncate(); | ||
|
|
||
| REVOKE TRUNCATE ON candidate_withdrawal_record FROM PUBLIC; | ||
|
|
||
| ALTER TABLE candidate_withdrawal_record ENABLE ROW LEVEL SECURITY; | ||
| ALTER TABLE candidate_withdrawal_record FORCE ROW LEVEL SECURITY; | ||
| CREATE POLICY candidate_withdrawal_scope_policy ON public.candidate_withdrawal_record | ||
| USING (tenant_record_id = public.current_tenant_record_id()) | ||
| WITH CHECK (tenant_record_id = public.current_tenant_record_id()); | ||
|
|
||
| COMMIT; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.