-
Notifications
You must be signed in to change notification settings - Fork 0
fix(criterion): reject future-dated performance evidence #72
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
9
commits into
develop
Choose a base branch
from
fix/criterion-observation-chronology
base: develop
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
9 commits
Select commit
Hold shift + click to select a range
f96b12b
test(criterion): reject observations recorded before occurrence
seonghobae d7f70ea
fix(criterion): enforce observation chronology
seonghobae 134bf24
fix(provenance): refresh criterion migration manifest
seonghobae 525dedc
fix(provenance): restore complete criterion manifest
seonghobae 90cdb78
docs: trace criterion chronology contract
seonghobae 4d5f6e3
fix: upgrade criterion chronology guard
seonghobae 3a8adb6
fix: reject future criterion observations
seonghobae 585498a
fix: close criterion recording-time gap
seonghobae a8ddab5
fix: use statement time for criterion observation defaults
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
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
173 changes: 173 additions & 0 deletions
173
database/migrations/0014_criterion_observation_chronology.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,173 @@ | ||
| -- Add chronology protection without rewriting the already released 0011 migration. | ||
| -- CREATE OR REPLACE preserves criterion_observation_scope_guard's binding while | ||
| -- upgrading databases that have already installed enforce_criterion_observation_scope(). | ||
|
|
||
| CREATE OR REPLACE FUNCTION enforce_criterion_observation_scope() | ||
| RETURNS trigger | ||
| LANGUAGE plpgsql | ||
| SET search_path = pg_catalog, public | ||
| AS $$ | ||
| DECLARE | ||
| observation_effective_date date; | ||
| criterion_job_profile_id uuid; | ||
| BEGIN | ||
| -- An immutable performance observation cannot truthfully enter system time | ||
| -- before the event it claims to have observed. Reject impossible chronology | ||
| -- before using the claimed observation instant for any scope lookup. | ||
| IF NEW.recorded_from < NEW.observed_at THEN | ||
| RAISE EXCEPTION 'criterion observation cannot be recorded before it was observed' | ||
| USING ERRCODE = '23514'; | ||
| END IF; | ||
|
seonghobae marked this conversation as resolved.
seonghobae marked this conversation as resolved.
seonghobae marked this conversation as resolved.
seonghobae marked this conversation as resolved.
|
||
|
|
||
| -- Effective periods in the current foundation are date-granular. Convert | ||
| -- the evidence instant through UTC explicitly so session TimeZone cannot | ||
| -- move an observation across a date boundary and bypass temporal checks. | ||
| observation_effective_date := (NEW.observed_at AT TIME ZONE 'UTC')::date; | ||
|
|
||
| SELECT blueprint.job_profile_id | ||
| INTO criterion_job_profile_id | ||
| FROM criterion_blueprint AS blueprint | ||
| WHERE blueprint.tenant_record_id = NEW.tenant_record_id | ||
| AND blueprint.criterion_blueprint_id = NEW.criterion_blueprint_id | ||
| AND blueprint.effective_from <= observation_effective_date | ||
| AND ( | ||
| blueprint.effective_to IS NULL | ||
| OR observation_effective_date < blueprint.effective_to | ||
| ) | ||
| AND blueprint.recorded_from <= statement_timestamp() | ||
| AND ( | ||
| blueprint.recorded_to IS NULL | ||
| OR statement_timestamp() < blueprint.recorded_to | ||
| ); | ||
|
|
||
| IF criterion_job_profile_id IS NULL THEN | ||
| RAISE EXCEPTION 'criterion observation references a criterion outside its effective or current-recorded period' | ||
| USING ERRCODE = '23514'; | ||
| END IF; | ||
|
|
||
| IF NOT EXISTS ( | ||
| SELECT 1 | ||
| FROM performance_cycle AS cycle_record | ||
| WHERE cycle_record.tenant_record_id = NEW.tenant_record_id | ||
| AND cycle_record.performance_cycle_id = NEW.performance_cycle_id | ||
| AND cycle_record.effective_from <= observation_effective_date | ||
| AND ( | ||
| cycle_record.effective_to IS NULL | ||
| OR observation_effective_date < cycle_record.effective_to | ||
| ) | ||
| AND cycle_record.recorded_from <= statement_timestamp() | ||
| AND ( | ||
| cycle_record.recorded_to IS NULL | ||
| OR statement_timestamp() < cycle_record.recorded_to | ||
| ) | ||
| ) THEN | ||
| RAISE EXCEPTION 'criterion observation is outside the performance cycle effective period' | ||
| USING ERRCODE = '23514'; | ||
| END IF; | ||
|
|
||
| IF NOT EXISTS ( | ||
| SELECT 1 | ||
| FROM assignment_record AS assignment | ||
| JOIN position_record AS position | ||
| ON position.tenant_record_id = assignment.tenant_record_id | ||
| AND position.position_record_id = assignment.position_record_id | ||
| WHERE assignment.tenant_record_id = NEW.tenant_record_id | ||
| AND assignment.person_record_id = NEW.person_record_id | ||
| AND position.job_profile_id = criterion_job_profile_id | ||
| AND assignment.effective_from <= observation_effective_date | ||
| AND ( | ||
| assignment.effective_to IS NULL | ||
| OR observation_effective_date < assignment.effective_to | ||
| ) | ||
| AND assignment.recorded_from <= statement_timestamp() | ||
| AND ( | ||
| assignment.recorded_to IS NULL | ||
| OR statement_timestamp() < assignment.recorded_to | ||
| ) | ||
| AND position.recorded_from <= statement_timestamp() | ||
| AND ( | ||
| position.recorded_to IS NULL | ||
| OR statement_timestamp() < position.recorded_to | ||
| ) | ||
| ) THEN | ||
| RAISE EXCEPTION 'criterion observation does not match an effective worker assignment for the criterion job' | ||
| USING ERRCODE = '23514'; | ||
| END IF; | ||
|
|
||
| -- A stale assignment anchor must not make a terminated employment or a | ||
| -- closed/frozen/abolished seat look like valid performance context. Reuse | ||
| -- the same status semantics as the HRIS assignment kernel and require one | ||
| -- *single* matching assignment to have both eligible employment and a | ||
| -- staffable position at the observation coordinate. | ||
| IF NOT EXISTS ( | ||
| SELECT 1 | ||
| FROM assignment_record AS assignment | ||
| JOIN position_record AS position | ||
| ON position.tenant_record_id = assignment.tenant_record_id | ||
| AND position.position_record_id = assignment.position_record_id | ||
| JOIN employment_record_version AS employment_version | ||
| ON employment_version.tenant_record_id = assignment.tenant_record_id | ||
| AND employment_version.employment_record_id = assignment.employment_record_id | ||
| JOIN position_record_version AS position_version | ||
| ON position_version.tenant_record_id = assignment.tenant_record_id | ||
| AND position_version.position_record_id = assignment.position_record_id | ||
| WHERE assignment.tenant_record_id = NEW.tenant_record_id | ||
| AND assignment.person_record_id = NEW.person_record_id | ||
| AND position.job_profile_id = criterion_job_profile_id | ||
| AND assignment.effective_from <= observation_effective_date | ||
| AND ( | ||
| assignment.effective_to IS NULL | ||
| OR observation_effective_date < assignment.effective_to | ||
| ) | ||
| AND assignment.recorded_from <= statement_timestamp() | ||
| AND ( | ||
| assignment.recorded_to IS NULL | ||
| OR statement_timestamp() < assignment.recorded_to | ||
| ) | ||
| AND position.recorded_from <= statement_timestamp() | ||
| AND ( | ||
| position.recorded_to IS NULL | ||
| OR statement_timestamp() < position.recorded_to | ||
| ) | ||
| AND employment_version.employment_status_code IN ('active', 'leave') | ||
| AND employment_version.effective_from <= observation_effective_date | ||
| AND ( | ||
| employment_version.effective_to IS NULL | ||
| OR observation_effective_date < employment_version.effective_to | ||
| ) | ||
| AND employment_version.recorded_from <= statement_timestamp() | ||
| AND ( | ||
| employment_version.recorded_to IS NULL | ||
| OR statement_timestamp() < employment_version.recorded_to | ||
| ) | ||
| AND position_version.position_status_code IN ('active', 'open') | ||
| AND position_version.effective_from <= observation_effective_date | ||
| AND ( | ||
| position_version.effective_to IS NULL | ||
| OR observation_effective_date < position_version.effective_to | ||
| ) | ||
| AND position_version.recorded_from <= statement_timestamp() | ||
| AND ( | ||
| position_version.recorded_to IS NULL | ||
| OR statement_timestamp() < position_version.recorded_to | ||
| ) | ||
| ) THEN | ||
| RAISE EXCEPTION 'criterion observation lacks an assignment with eligible employment and staffable position coverage' | ||
| USING ERRCODE = '23514'; | ||
| END IF; | ||
|
|
||
| -- A caller may order both supplied timestamps after one another while still | ||
| -- claiming an event that has not happened at the database's current time. | ||
| IF NEW.observed_at > statement_timestamp() THEN | ||
| RAISE EXCEPTION 'criterion observation cannot be observed in the future' | ||
| USING ERRCODE = '23514'; | ||
| END IF; | ||
|
seonghobae marked this conversation as resolved.
|
||
|
|
||
| IF NEW.recorded_from > statement_timestamp() THEN | ||
| RAISE EXCEPTION 'criterion observation cannot be recorded in the future' | ||
| USING ERRCODE = '23514'; | ||
| END IF; | ||
|
seonghobae marked this conversation as resolved.
|
||
|
|
||
| RETURN NEW; | ||
| END; | ||
| $$; | ||
5 changes: 5 additions & 0 deletions
5
database/migrations/0015_criterion_observation_statement_default.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,5 @@ | ||
| -- Record omitted criterion-observation timestamps at statement time. | ||
| -- The transaction timestamp can precede a real observation in a long transaction. | ||
|
|
||
| ALTER TABLE criterion_observation | ||
| ALTER COLUMN recorded_from SET DEFAULT statement_timestamp(); | ||
|
seonghobae marked this conversation as resolved.
|
||
Oops, something went wrong.
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.