Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
196e853
test: define governed offer approval contract
seonghobae Aug 18, 2026
f945bff
feat: implement governed offer approval packet
seonghobae Aug 18, 2026
b7294d8
test: reject value-bearing offer references
seonghobae Aug 18, 2026
2ae8117
fix: require opaque UUID offer references
seonghobae Aug 18, 2026
c4539f4
docs: align offer references with opaque UUID contract
seonghobae Aug 18, 2026
4917c06
test: require authoritative offer actor separation
seonghobae Aug 19, 2026
46f73fe
fix: require authoritative offer actor separation
seonghobae Aug 19, 2026
9a4783f
docs: require authoritative offer actor separation
seonghobae Aug 19, 2026
f0f8e75
docs: bind offer approval to resolved actors
seonghobae Aug 19, 2026
1f03ef5
docs: trace authoritative offer actor separation
seonghobae Aug 19, 2026
193cfc9
test: require redacted offer approval repr
seonghobae Aug 19, 2026
9336f05
fix: redact offer approval evidence repr
seonghobae Aug 19, 2026
caeae7d
chore: reconcile offer approval with protected develop
seonghobae Aug 19, 2026
738f520
chore: reconcile offer approval with protected develop
seonghobae Aug 19, 2026
f93ff95
test: cover direct offer packet construction
seonghobae Aug 19, 2026
21a0dbc
test: reject value-bearing offer reason codes
seonghobae Aug 19, 2026
04a3fa1
fix: close offer reason metadata vocabulary
seonghobae Aug 19, 2026
372a8bd
docs: document closed offer reason vocabulary
seonghobae Aug 19, 2026
172088c
docs: bind offer reason to reviewed contract
seonghobae Aug 19, 2026
01dcbb3
docs: trace offer reason minimization regression
seonghobae Aug 19, 2026
2ee0ece
docs: record offer reason privacy hardening
seonghobae Aug 19, 2026
4d03ee2
fix: align actor-separation fixture with governed offer reason
seonghobae Aug 19, 2026
87ec227
test: require offer approval evidence versioning
seonghobae Aug 19, 2026
ecf257b
fix: bind offer approval evidence version
seonghobae Aug 19, 2026
35500da
docs: document offer evidence versioning
seonghobae Aug 19, 2026
7871962
docs: bind evidence version in offer ADR
seonghobae Aug 19, 2026
7f0df90
docs: trace offer evidence versioning
seonghobae Aug 19, 2026
0d57cbe
docs: record offer evidence versioning
seonghobae Aug 19, 2026
7d2fa76
Merge protected develop into offer approval lane
seonghobae Aug 19, 2026
68498cc
chore: reconcile offer approval onto develop
seonghobae Aug 19, 2026
4e47cc9
test(offer-approval): require tenant-scoped reference resolution
seonghobae Aug 19, 2026
f98d2c9
fix(offer-approval): bind all evidence to tenant scope
seonghobae Aug 19, 2026
21a209f
docs(offer-approval): align tenant evidence boundary
seonghobae Aug 19, 2026
cbf31d4
docs(offer-approval): require exact-tenant evidence resolution
seonghobae Aug 19, 2026
665a61f
merge: reconcile offer approval with protected develop
seonghobae Aug 19, 2026
919225c
test(offer-approval): align tenant next-action assertion
seonghobae Aug 19, 2026
e21cd75
chore(offer-approval): reconcile protected develop
seonghobae Aug 19, 2026
ae51aa6
test: reject UUIDv1 offer approval trust references
seonghobae Aug 20, 2026
45ecd10
fix: require UUIDv4 offer approval trust references
seonghobae Aug 20, 2026
301dc3f
docs: define UUIDv4 offer reference privacy contract
seonghobae Aug 20, 2026
7ab39cd
docs: record UUIDv4 trust-reference decision
seonghobae Aug 20, 2026
741328a
docs: record offer reference privacy hardening
seonghobae Aug 20, 2026
722286f
docs: trace UUIDv4 offer reference regression
seonghobae Aug 20, 2026
0a7e365
test: reject correlating tenant UUIDv1 in offer approval
seonghobae Aug 20, 2026
d15f6bb
fix: require opaque UUIDv4 tenant identity in offer approval
seonghobae Aug 20, 2026
658eec1
docs: bind offer-approval tenant identity to UUIDv4 opacity
seonghobae Aug 20, 2026
2b4dfa8
docs: require UUIDv4 tenant opacity in offer approval ADR
seonghobae Aug 20, 2026
dd3b482
docs: trace UUIDv4 tenant opacity in offer approval
seonghobae Aug 20, 2026
2975c0c
docs: record offer-approval tenant UUIDv4 hardening
seonghobae Aug 20, 2026
f2d8081
Merge remote-tracking branch 'refs/remotes/origin/develop' into HEAD
seonghobae Aug 20, 2026
c481792
test: require offer approval to accept core tenant UUIDv7
seonghobae Aug 20, 2026
01cc7bc
fix: honor authoritative tenant UUID contract in offer approval
seonghobae Aug 20, 2026
7bd84ab
docs: align offer tenant identity with Orgmetra core
seonghobae Aug 20, 2026
8419527
docs: separate offer tenant and packet UUID ownership
seonghobae Aug 20, 2026
9810102
docs: trace offer tenant UUID interoperability
seonghobae Aug 20, 2026
ed0f67a
docs: record offer tenant identity interoperability repair
seonghobae Aug 20, 2026
64a2566
chore: integrate protected develop into offer-approval lane
seonghobae Aug 20, 2026
af6babf
chore: reconcile offer approval with current develop
seonghobae Aug 20, 2026
8af2e3b
chore(offer): integrate protected develop after #41
seonghobae Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/offer-approval-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Offer Approval Quality

on:
pull_request:
branches:
- develop
paths:
- "packages/offer-approval/**"
- ".github/requirements/foundation-test.txt"
- ".github/workflows/offer-approval-quality.yml"
- "docs/adr/0017-governed-offer-approval.md"
- "docs/doctoring/offer-approval-references.md"
- "docs/traceability/offer-approval.md"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: offer-approval-quality-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
unit:
name: Offer approval contract and 100% coverage
runs-on: ubuntu-latest
timeout-minutes: 10
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: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
check-latest: false
- name: Install reviewed test toolchain
run: |
python -m pip install --require-hashes --no-deps --only-binary=:all: -r .github/requirements/foundation-test.txt
python -m pip check
- name: Compile offer approval package
run: python -m compileall -q packages/offer-approval/src packages/offer-approval/tests
- name: Test offer approval with exact statement and branch coverage
env:
PYTHONPATH: packages/offer-approval/src
COVERAGE_FILE: /tmp/orgmetra-offer-approval.coverage
run: python -m pytest -c packages/offer-approval/pyproject.toml packages/offer-approval/tests
- name: Require clean checkout
run: |
git diff --exit-code
test -z "$(git status --porcelain)"
37 changes: 37 additions & 0 deletions docs/adr/0017-governed-offer-approval.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ADR 0017: Governed offer approval evidence

- Status: Proposed — active PR only
- Date: 2026-08-19
- Scope: Talent Acquisition offer review

## Context

Protected `develop` can govern candidate, requisition, selection, and employment evidence, but it does not yet expose a bounded pre-send contract proving that a proposed offer is tied to the selected candidate, authoritative Job/optional Position, reviewed selection decision, compensation-package provenance, offer-terms provenance, and accountable human approval.

Offer review is high-impact employment workflow. A governance envelope must not become an alternate decision authority, a salary-value cache, or a channel that lets generated/model material masquerade as an approved offer. Different opaque requester/approver references also do not prove that the authoritative actor boundary resolves them to different people, and UUID syntax does not prove that the referenced candidate, requisition, Job/Position, selection decision, compensation package, or offer terms belong to the packet tenant. Packet-owned UUIDv1 references also carry timestamp/node-derived correlation metadata. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy.

ISO 30405:2023 provides current recruitment guidance across planning, assessment, employment, stakeholder management, and review. EEOC guidance on tests and selection procedures emphasizes job-related use and employer responsibility for selection procedures. Those sources support a conservative evidence-and-human-review boundary; they do not by themselves certify this package or decide the legality of any offer.

## Decision

Orgmetra will expose `OfferApprovalPacket` as value-free review evidence only.

`tenant_record_id` must be canonical and non-sentinel under Orgmetra's authoritative operational UUID contract. Tenant UUID generation/version/privacy policy remains owned by the core HRIS boundary. Packet-owned opaque candidate profile, requisition, Job, optional Position, selection decision, compensation package, offer terms, and accountable actor references separately require canonical non-sentinel UUIDv4 plus their expected namespace. UUIDv1 and other non-v4 suffixes fail closed for those packet-owned references. Decision, package, and terms artifacts are independently SHA-256 bound. Before approval, the host must re-resolve **every packet reference** within the exact `tenant_record_id` through its authoritative boundary and reject approval if any reference belongs to another tenant or cannot be authoritatively resolved. Identical requester/approver references are rejected as an early syntactic guard; after tenant-scoped resolution, the host must prove their resolved actor identities are distinct. Reference inequality alone is not separation-of-duties evidence.

The packet must not contain candidate PII, compensation values, assessment scores, or free-form model output. The `reason_code` field is closed to the reviewed value-free `selected_candidate_offer_review` code. Direct construction and `dataclasses.replace(...)` revalidate all trust-bearing invariants.

Every packet is fixed to purpose `offer_approval_review`, reviewed reason `selected_candidate_offer_review`, bounded positive integer `evidence_version` (default `1`) included in canonical JSON/SHA-256, `human_confirmation_required=True`, decision authority `human_approval_only`, review state `requires_human_approval`, and delivery state `not_authorized_to_send`.

`evidence_version` accepts only real integers from `1` through `2147483647`; booleans, text, zero, negative values, and overflow values fail closed. It versions the immutable pre-send evidence envelope and does not itself prove source-version resolution, approval, or delivery.

Canonical JSON and SHA-256 are audit-correlation evidence only. The packet does not approve, communicate, send, execute, persist an offer, or prove authoritative reference/actor identity.

## Consequences

A buyer can review one deterministic, PII-minimized envelope before an offer moves to the authoritative offer workflow. Compensation values stay in their purpose-bound owner boundary, while Orgmetra keeps exact provenance references, evidence version, and human accountability. Packet-owned UUIDv1/non-v4 references fail closed before serialization without making this leaf package incompatible with authoritative Orgmetra tenant UUIDs. Cross-tenant evidence mixing is fail-closed at the host approval boundary because every packet reference must resolve in the exact tenant. Requester/approver separation is proven only after tenant-scoped authoritative actor resolution. New offer-review reason categories require an explicit contract change and regression evidence rather than accepting arbitrary caller text.

Downstream offer persistence/execution must independently enforce authorization, tenant-scoped source-evidence resolution, idempotency where applicable, and immutable audit/outbox evidence. This ADR remains proposed active-PR truth until integrated into protected `develop`.

## References

See `docs/doctoring/offer-approval-references.md`.
22 changes: 22 additions & 0 deletions docs/doctoring/offer-approval-references.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Offer approval references

Retrieved August 19, 2026.
Comment thread
seonghobae marked this conversation as resolved.

International Organization for Standardization. (2023). *ISO 30405:2023 Human resource
management—Guidelines on recruitment* (2nd ed.). https://www.iso.org/standard/79488.html

U.S. Equal Employment Opportunity Commission. (2007, December 1). *Employment tests and
selection procedures*. https://www.eeoc.gov/laws/guidance/employment-tests-and-selection-procedures

U.S. Equal Employment Opportunity Commission, U.S. Department of Justice, U.S. Department of
Labor, U.S. Office of Personnel Management, & U.S. Department of the Treasury. (1979, March 1).
*Questions and answers to clarify and provide a common interpretation of the Uniform Guidelines
on Employee Selection Procedures*. https://www.eeoc.gov/laws/guidance/questions-and-answers-clarify-and-provide-common-interpretation-uniform-guidelines

## Applied boundary

These sources support recruitment-process governance, job-related selection responsibility,
stakeholder review, and documented use of selection evidence. This package makes no
certification or legal-compliance claim. It deliberately keeps candidate and compensation values
outside the governance envelope and requires accountable human approval before any offer may be
communicated or executed.
21 changes: 21 additions & 0 deletions docs/traceability/offer-approval.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Governed offer approval traceability

Status: **active PR / proposed capability**, not protected-main truth.

| Buyer requirement | Executable evidence | Contract outcome |
| --- | --- | --- |
| Exact selected-candidate scope | `test_rejects_bad_opaque_references`; canonical JSON test | Candidate is correlated only by a bounded opaque `candidate_profile:` reference. |
| Authoritative tenant interoperability and packet-reference privacy | `test_tenant_identity_privacy.py`; `test_rejects_uuid1_trust_references_through_direct_and_replace` | `tenant_record_id` follows the canonical non-sentinel Orgmetra core operational-UUID contract; packet-owned namespaced references require canonical non-sentinel UUIDv4 and reject UUIDv1/non-v4 suffixes through construction and replacement paths. |
| Separate Job and Position | valid packet + optional-Position test | Job is mandatory; Position is separately named and optional rather than collapsed into Job. |
| Reviewed selection evidence | digest/reference validation tests | Selection decision identity and SHA-256 evidence are required. |
| Compensation/terms provenance without value duplication | value-free canonical JSON test; digest/reference validation tests | Package and terms are exact reference+digest pairs; salary/benefit values are absent. |
| Value-free reason metadata | `test_rejects_value_bearing_reason_codes_through_direct_and_replace` | `reason_code` is closed to reviewed `selected_candidate_offer_review`; arbitrary lower-snake-case candidate, compensation, or offer-term text fails closed. |
| Human accountability and separation of duties | same-reference rejection plus `test_actor_separation.py` | Requester/approver references differ locally, and approval requires tenant-scoped authoritative resolution proving distinct resolved actor identities. |
| High-impact evidence versioning | `test_evidence_version.py` | Bounded positive `evidence_version` is in canonical JSON, changes correlation SHA-256 across versions, and revalidates through mutation-by-copy. |
| No premature offer delivery | direct-constructor/replace fail-closed tests | State remains `requires_human_approval` and `not_authorized_to_send`. |
| Deterministic audit correlation | canonical JSON, fractional-second, timezone, evidence-version, SHA-256 tests | Canonical evidence is precision-preserving, versioned, and deterministic. |
| Public API readability | module/class/function docstrings | Beginner-readable contract boundary is documented in source and package README. |

UUIDv4 is an opacity constraint for packet-owned trust references, not tenant authority. Tenant UUID generation/version/privacy policy remains owned by the authoritative HRIS boundary. Before approval, every packet reference must still resolve authoritatively inside the exact `tenant_record_id`; requester/approver identity separation must be proven after that resolution.

The SHA-256 packet digest proves only the exact canonical envelope bytes. It does not prove that referenced evidence is substantively correct, that requester/approver resolve to different identities, that compensation is lawful/fair, that a human approved the offer, or that an offer was delivered. Authoritative actor and source-evidence resolution remain outside this evidence packet and are required pre-approval host checks.
11 changes: 11 additions & 0 deletions packages/offer-approval/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changelog

## Unreleased

- Add a governed, value-free pre-send offer approval packet.
- Require separate requester and approver identities and exact human approval.
- Bind selected-candidate, Job/optional Position, selection-decision, compensation-package, and offer-terms provenance without copying candidate or compensation values.
- Follow Orgmetra's authoritative canonical non-sentinel operational UUID contract for `tenant_record_id`, while packet-owned trust-reference suffixes remain canonical non-sentinel UUIDv4 and reject UUIDv1/non-v4 identity forms.
- Close `reason_code` to the reviewed value-free `selected_candidate_offer_review` contract so arbitrary candidate, compensation, or offer-term text cannot enter canonical evidence.
- Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact offer-review evidence versions are explicit and fail closed.
- Keep every packet `requires_human_approval` and `not_authorized_to_send`.
40 changes: 40 additions & 0 deletions packages/offer-approval/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Orgmetra governed offer approval

This package creates a **value-free pre-send offer approval packet**. It is a governance envelope, not an offer engine and not an employment decision.

The packet binds one selected candidate to the exact requisition and authoritative Job, an optional exact Position, the reviewed selection-decision digest, compensation-package provenance, offer-terms provenance, and two accountable actor references. Identical requester and approver references are rejected as an early syntactic guard.

The envelope intentionally excludes candidate names, email addresses, demographic values, assessment scores, salary/benefit amounts, credentials, and free-form model output. `candidate_profile_reference` remains sensitive correlating metadata even though it is opaque. `tenant_record_id` follows Orgmetra's authoritative canonical non-sentinel operational UUID contract rather than imposing a second UUID-version rule in this leaf package. Packet-owned namespaced references remain canonical non-sentinel UUIDv4 values and require their expected namespace. UUIDv1 and other non-v4 reference suffixes are rejected so timestamp/node correlation metadata, names, compensation values, offer terms, and actor identities cannot be smuggled into packet-owned governance references. `reason_code` is likewise closed to the reviewed, value-free `selected_candidate_offer_review` code; arbitrary lower-snake-case text is rejected so the reason field cannot become a side channel for candidate, compensation, or offer-term values.

Every packet also carries a bounded positive integer `evidence_version` (default `1`). It is serialized into canonical JSON, so changing the governed evidence version changes the packet SHA-256 digest. Zero, negative, boolean, textual, and values above `2147483647` fail closed. The field versions this immutable pre-send evidence envelope; it is not approval, delivery, or proof that referenced source versions were authoritatively resolved.

A valid packet always remains `requires_human_approval` and `not_authorized_to_send`. Before approval, the host must re-resolve **every packet reference** within the exact `tenant_record_id` through its authoritative boundary so valid references from a foreign tenant cannot be mixed into the approval envelope. It must specifically re-resolve `requester_reference` and `approver_reference` and prove their resolved actor identities are distinct; opaque-reference inequality alone is not separation-of-duties evidence. The host must then verify Job/Position scope, selected-candidate evidence, compensation-package provenance, and offer-terms provenance before recording accountable human approval through the authoritative offer workflow and before communicating or executing the offer. UUIDv4 is only an opacity constraint for packet-owned references; tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary.

Canonical JSON and its SHA-256 digest support immutable audit correlation. They do not prove that the referenced evidence is true, that all references belong to the packet tenant, that actor identities are distinct, that compensation is lawful or fair, that an offer was approved, or that an offer was communicated.

## Example

```python
from datetime import datetime, timezone
from orgmetra_offer_approval import build_offer_approval_packet

packet = build_offer_approval_packet(
tenant_record_id="11111111-1111-4111-8111-111111111111",
offer_approval_reference="offer_approval:10000000-0000-4000-8000-000000000001",
candidate_profile_reference="candidate_profile:10000000-0000-4000-8000-000000000002",
requisition_reference="requisition:10000000-0000-4000-8000-000000000003",
job_profile_reference="job_profile:10000000-0000-4000-8000-000000000004",
position_record_reference="position_record:10000000-0000-4000-8000-000000000005",
selection_decision_reference="selection_decision:10000000-0000-4000-8000-000000000006",
selection_decision_digest="a" * 64,
compensation_package_reference="compensation_package:10000000-0000-4000-8000-000000000007",
compensation_package_digest="b" * 64,
offer_terms_reference="offer_terms:10000000-0000-4000-8000-000000000008",
offer_terms_digest="c" * 64,
requester_reference="actor:10000000-0000-4000-8000-000000000009",
approver_reference="actor:10000000-0000-4000-8000-00000000000a",
purpose_code="offer_approval_review",
reason_code="selected_candidate_offer_review",
generated_at=datetime.now(timezone.utc),
)
```
24 changes: 24 additions & 0 deletions packages/offer-approval/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[build-system]
requires = ["setuptools>=69"]
build-backend = "setuptools.build_meta"

[project]
name = "orgmetra-offer-approval"
version = "0.1.0"
description = "Governed human offer-approval evidence for Orgmetra."
requires-python = ">=3.12"

[project.optional-dependencies]
test = ["pytest>=8.3", "pytest-cov>=5.0"]

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = [
"--cov=orgmetra_offer_approval",
"--cov-branch",
"--cov-report=term-missing",
"--cov-fail-under=100",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Public governed offer-approval contract."""

from .packet import OfferApprovalPacket, build_offer_approval_packet

__all__ = ["OfferApprovalPacket", "build_offer_approval_packet"]
Loading
Loading