Preserve fractional approval expiry through atomic consumption
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
This commit is contained in:
parent
3485415b82
commit
d40e842052
6 changed files with 271 additions and 6 deletions
|
|
@ -36,7 +36,8 @@ Canonical float/string formatting follows Python source, including negative
|
||||||
floating zero, exponent switching/padding, HTML and U+2028/U+2029. Duplicate
|
floating zero, exponent switching/padding, HTML and U+2028/U+2029. Duplicate
|
||||||
object keys, nonfinite/overflow numbers, invalid UTF-8 and lone surrogates refuse.
|
object keys, nonfinite/overflow numbers, invalid UTF-8 and lone surrogates refuse.
|
||||||
Inputs are bounded to 1 MiB and 128 container levels. Timestamp precision is at
|
Inputs are bounded to 1 MiB and 128 container levels. Timestamp precision is at
|
||||||
most nanoseconds with strict zoned RFC3339; current producer emits whole seconds.
|
most nanoseconds with strict zoned RFC3339; the precision-repaired Python
|
||||||
|
producer preserves microseconds, while historical whole-second strings remain valid.
|
||||||
These explicit parser/resource limits fail closed, never normalize authority.
|
These explicit parser/resource limits fail closed, never normalize authority.
|
||||||
|
|
||||||
`go test -race ./...` checks the portable producer vectors, typed fact pairing,
|
`go test -race ./...` checks the portable producer vectors, typed fact pairing,
|
||||||
|
|
|
||||||
|
|
@ -103,11 +103,11 @@ def utc_now(clock: Callable[[], datetime] | None = None) -> datetime:
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
if now.tzinfo is None:
|
if now.tzinfo is None:
|
||||||
now = now.replace(tzinfo=timezone.utc)
|
now = now.replace(tzinfo=timezone.utc)
|
||||||
return now.astimezone(timezone.utc).replace(microsecond=0)
|
return now.astimezone(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
def iso(ts: datetime) -> str:
|
def iso(ts: datetime) -> str:
|
||||||
return ts.astimezone(timezone.utc).replace(microsecond=0).isoformat()
|
return ts.astimezone(timezone.utc).isoformat()
|
||||||
|
|
||||||
|
|
||||||
def parse_iso(value: str) -> datetime:
|
def parse_iso(value: str) -> datetime:
|
||||||
|
|
@ -768,7 +768,6 @@ class Engine:
|
||||||
):
|
):
|
||||||
raise Unprocessable("decision_id must be a non-empty string")
|
raise Unprocessable("decision_id must be a non-empty string")
|
||||||
conn = self._conn()
|
conn = self._conn()
|
||||||
now = iso(self.now())
|
|
||||||
idempotent = False
|
idempotent = False
|
||||||
try:
|
try:
|
||||||
conn.execute("BEGIN IMMEDIATE")
|
conn.execute("BEGIN IMMEDIATE")
|
||||||
|
|
@ -794,7 +793,11 @@ class Engine:
|
||||||
if row["status"] != "approved":
|
if row["status"] != "approved":
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
raise Conflict(f"cannot consume from status {row['status']}")
|
raise Conflict(f"cannot consume from status {row['status']}")
|
||||||
if self.now() < parse_iso(row["not_before"]) or self.now() >= parse_iso(row["expires_at"]):
|
# Lock acquisition can wait past a validity boundary. One post-lock
|
||||||
|
# instant is authoritative for both the check and its durable proof.
|
||||||
|
consumed_at = self.now()
|
||||||
|
now = iso(consumed_at)
|
||||||
|
if consumed_at < parse_iso(row["not_before"]) or consumed_at >= parse_iso(row["expires_at"]):
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
raise Conflict("cannot consume outside validity window")
|
raise Conflict("cannot consume outside validity window")
|
||||||
if row["human_control"]:
|
if row["human_control"]:
|
||||||
|
|
@ -819,6 +822,7 @@ class Engine:
|
||||||
"use",
|
"use",
|
||||||
approval_id,
|
approval_id,
|
||||||
actor=row["actor"],
|
actor=row["actor"],
|
||||||
|
observed_at=consumed_at,
|
||||||
extra={
|
extra={
|
||||||
"binding_digest": row["binding_digest"],
|
"binding_digest": row["binding_digest"],
|
||||||
"binding": json.loads(row["binding_json"]),
|
"binding": json.loads(row["binding_json"]),
|
||||||
|
|
@ -911,13 +915,14 @@ class Engine:
|
||||||
*,
|
*,
|
||||||
actor: str | None,
|
actor: str | None,
|
||||||
extra: dict[str, Any] | None = None,
|
extra: dict[str, Any] | None = None,
|
||||||
|
observed_at: datetime | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
if self.fail_outbox:
|
if self.fail_outbox:
|
||||||
raise StoreUnavailable("outbox insert failed")
|
raise StoreUnavailable("outbox insert failed")
|
||||||
if event_class not in EVENT_CLASSES:
|
if event_class not in EVENT_CLASSES:
|
||||||
raise Unprocessable(f"unknown event class {event_class}")
|
raise Unprocessable(f"unknown event class {event_class}")
|
||||||
event_id = str(uuid4())
|
event_id = str(uuid4())
|
||||||
created = iso(self.now())
|
created = iso(observed_at if observed_at is not None else self.now())
|
||||||
resource = f"approval:{approval_id}" if approval_id else "approval-engine:heartbeat"
|
resource = f"approval:{approval_id}" if approval_id else "approval-engine:heartbeat"
|
||||||
details = {"class": event_class, **(extra or {})}
|
details = {"class": event_class, **(extra or {})}
|
||||||
if approval_id:
|
if approval_id:
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,24 @@ after consumption, the approval remains spent and a retry needs a new approval.
|
||||||
The response is mutation evidence, not a permission decision. It contains no
|
The response is mutation evidence, not a permission decision. It contains no
|
||||||
`effect`, `allow`, `deny`, or decision result.
|
`effect`, `allow`, `deny`, or decision result.
|
||||||
|
|
||||||
|
## Authoritative time and fractional precision
|
||||||
|
|
||||||
|
New source preserves the actual aware UTC clock's microseconds rather than
|
||||||
|
rounding down to whole seconds. The first consume samples one instant after
|
||||||
|
`BEGIN IMMEDIATE` acquires its write lock, then uses that instant for the
|
||||||
|
not-before/inclusive and expiry/exclusive check, persisted `consumed_at` and
|
||||||
|
`updated_at`, and use-event occurrence/creation time. Lock wait cannot borrow
|
||||||
|
an earlier validity instant or stamp evidence before actual admission.
|
||||||
|
|
||||||
|
Existing whole-second ISO strings remain valid and are never backfilled with
|
||||||
|
invented fractions. Idempotent responses and object readback retain the original
|
||||||
|
recorded timestamp/decision exactly, even after expiry; they do not renew
|
||||||
|
execution authority. The store schema, API keys, CAS/digest correspondence and
|
||||||
|
no-unconsume rule are unchanged. Claim observation/freshness and other new
|
||||||
|
source timestamps also retain actual clock precision. Consumers must parse
|
||||||
|
aware ISO/RFC3339 timestamps including fractional seconds instead of assuming
|
||||||
|
a fixed-width whole-second string. No clock skew or early-use tolerance is added.
|
||||||
|
|
||||||
## The claim response is not an `ActionAuthorization`
|
## The claim response is not an `ActionAuthorization`
|
||||||
|
|
||||||
`GET /v1/approvals/{id}/claim` serves the **approval-claim** envelope defined in
|
`GET /v1/approvals/{id}/claim` serves the **approval-claim** envelope defined in
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"reviewer": "approval-engine-owner (agent approval-engine, codex)",
|
||||||
|
"decision": "approved",
|
||||||
|
"comment": "INTENT Purpose (valid right now), The Claim Shape and Consumption Ordering: approved for bounded Approval source time precision repair and Approval-owned fact/temporal correspondence in exact Access R3 source. Work APPROVAL-WP-0006-T01/T02. Repair patch SHA256 sha256:74eb0ff67d14508c2a2cee14d055790321ee171a96d5290ad6d037ce8f61dbcd against 3485415b823faf4f3b682079195e186666b0ed2b; store.py SHA256 sha256:866b275ed2d844d0aa9090ce9899ce955c6bfe32a493c2645c86e72efa343e86. Access R3 full patch SHA256 sha256:46bfeb706d3a88bf6054309cc9303eb360a91e57dda3b0fcbb7b80d551a75db6, delta sha256:17ecf399c50c07845c545b31cad4022200eb20dfb760595aa76a271e9cee7521; all 31 frozen source hashes matched. This review does not assent to Access policy/other-owner behavior, native credentials/trust, deployment or effects.",
|
||||||
|
"reviewed_repair_patch_sha256": "sha256:74eb0ff67d14508c2a2cee14d055790321ee171a96d5290ad6d037ce8f61dbcd",
|
||||||
|
"reviewed_access_full_r3_sha256": "sha256:46bfeb706d3a88bf6054309cc9303eb360a91e57dda3b0fcbb7b80d551a75db6",
|
||||||
|
"reviewed_access_r2_to_r3_sha256": "sha256:17ecf399c50c07845c545b31cad4022200eb20dfb760595aa76a271e9cee7521",
|
||||||
|
"approval_time_findings": [
|
||||||
|
"utc_now and iso preserve actual aware microseconds; no schema or binding algorithm change",
|
||||||
|
"First consume samples one authoritative time only after BEGINIMMEDIATE obtains the lock; identical instant checks validity, persists receipt/update and creates use outbox timestamp",
|
||||||
|
"Expiry remains exclusive; actual clock .750 refuses expiry .500; post-lock expiry cannot borrow pre-lock admission",
|
||||||
|
"Whole-second old rows, readback and idempotent original decision/timestamp are preserved without fabricated precision; no unconsume, retry authority or tolerance",
|
||||||
|
"No Access NotBefore backdating, caller wait/sleep acceptance or consumer timing waiver is needed"
|
||||||
|
],
|
||||||
|
"access_fact_temporal_findings": [
|
||||||
|
"Signed claim-free DENY normalization receipt supplies existing producer-evaluated native act and approval-free digest before issue, never permission; requester consumer independently authenticates it",
|
||||||
|
"Actual retained registry normalization is source-owned and number-preserving; final join compares the approved act/PDP digest and refuses drift instead of refreshing old context",
|
||||||
|
"Original and newly fetched facts are validated separately; no mutation of original claim/context or either request digest",
|
||||||
|
"Exact immutable operation deadline must fit caller, original/fetched freshness and approval validity; decision is precise post-evaluation NotBefore to exact operation deadline",
|
||||||
|
"Entire remaining interval must cover declared backendTTL plus 5-second cleanup reserve and source maximum 120; this review does not approve that policy or native budget",
|
||||||
|
"Default Approval freshness 30 cannot satisfy minimum 30+5; fixture freshness 120 is explicit synthetic configuration, not a producer default flip or native grant"
|
||||||
|
],
|
||||||
|
"validation": {
|
||||||
|
"repair_focused_passed": 89,
|
||||||
|
"repair_full_passed": 298,
|
||||||
|
"repair_full_failed_preexisting": 1,
|
||||||
|
"baseline_counterexamples_failed": 5,
|
||||||
|
"access_independent_command": "go test -race -count=1 ./internal/decision ./cmd/access-engine -run TestTrusted",
|
||||||
|
"access_independent_result": "passed"
|
||||||
|
},
|
||||||
|
"retained_failures": [
|
||||||
|
"coordinator-counterexample.json documents canonical expired fractional approval consumed",
|
||||||
|
"Initial repair expiry test attempted already-expired issuance; repaired engine correctly refused; fixture corrected to issue before expiry then advance",
|
||||||
|
"Existing deployment pin/historical release evidence mismatch remains unchanged"
|
||||||
|
],
|
||||||
|
"prior_evidence": "WP0005 closure and6079edR1review remain exact historical evidence; this is a new precision task and new exactR3 review",
|
||||||
|
"remaining_gates": [
|
||||||
|
"Coordinator independent source integration/publication/synchronization at exact repair and Access/Secrets successor source pins",
|
||||||
|
"Actual immediate strict CAS/whole-path remeasurement must use unchanged precise signed NotBefore and no sleep/waiver",
|
||||||
|
"Native issuer/profile, CA/source/read credential/store tenant admission and schema/image/config rollout remain existing WP0002T01",
|
||||||
|
"Actual use event/audit receiver reconciliation remains WP0002T04",
|
||||||
|
"Served PEP original decision/request CAS before effect, live one-shot journal, deadlines and confirmed cleanup remain WP0002T05 and existing Secrets/Platform work"
|
||||||
|
],
|
||||||
|
"native_accepted": false,
|
||||||
|
"authority_promotion": false
|
||||||
|
}
|
||||||
130
tests/test_timestamp_precision.py
Normal file
130
tests/test_timestamp_precision.py
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
"""Actual source clock, locking and original immutable receipt precision."""
|
||||||
|
from datetime import timedelta, timezone
|
||||||
|
import sqlite3
|
||||||
|
import threading
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from approval_engine.errors import Conflict, StoreUnavailable
|
||||||
|
from approval_engine.store import Engine, iso, utc_now
|
||||||
|
from tests.conftest import FROZEN, binding
|
||||||
|
|
||||||
|
DIGEST = "sha256:" + "ab" * 32
|
||||||
|
|
||||||
|
|
||||||
|
def issue(engine, start, end):
|
||||||
|
obj = engine.create(binding(), {"not_before": start.isoformat(), "expires_at": end.isoformat()})
|
||||||
|
return engine.add_entry(obj.id, "human", principal_type="human")
|
||||||
|
|
||||||
|
|
||||||
|
def uses(engine):
|
||||||
|
return [row for row in engine.undrained() if row["class"] == "use"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_clock_and_iso_preserve_aware_precision_and_legacy_seconds():
|
||||||
|
now = FROZEN.replace(microsecond=750123)
|
||||||
|
offset = now.astimezone(timezone(timedelta(hours=2)))
|
||||||
|
assert utc_now(lambda: offset) == now
|
||||||
|
assert iso(offset) == "2026-08-29T12:00:00.750123+00:00"
|
||||||
|
assert iso(FROZEN) == "2026-08-29T12:00:00+00:00"
|
||||||
|
|
||||||
|
|
||||||
|
def test_fractional_expiry_refuses_actual_canonical_counterexample():
|
||||||
|
engine = Engine(clock=lambda: FROZEN)
|
||||||
|
obj = issue(engine, FROZEN-timedelta(seconds=1), FROZEN.replace(microsecond=500000))
|
||||||
|
engine.clock = lambda: FROZEN.replace(microsecond=750000)
|
||||||
|
assert engine.claim(obj.id)["valid_now"] is False
|
||||||
|
with pytest.raises(Conflict):
|
||||||
|
engine.consume(obj.id, DIGEST, decision_id="decision:original")
|
||||||
|
assert uses(engine) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_immediate_fractional_receipt_restart_and_outbox_use_one_instant(tmp_path):
|
||||||
|
current = [FROZEN.replace(microsecond=750123)]
|
||||||
|
path = tmp_path/"approval.sqlite"
|
||||||
|
engine = Engine(path, clock=lambda: current[0])
|
||||||
|
obj = issue(engine, FROZEN.replace(microsecond=700000), FROZEN+timedelta(seconds=1))
|
||||||
|
samples = []
|
||||||
|
def clock():
|
||||||
|
value = current[0] + timedelta(microseconds=len(samples))
|
||||||
|
samples.append(value)
|
||||||
|
return value
|
||||||
|
engine.clock = clock
|
||||||
|
result = engine.consume(obj.id, DIGEST, decision_id="decision:original")
|
||||||
|
assert samples == [current[0]]
|
||||||
|
assert result["consumed_at"] == current[0].isoformat()
|
||||||
|
assert result["consumed_at"] >= FROZEN.replace(microsecond=700001).isoformat()
|
||||||
|
event = uses(engine)[0]
|
||||||
|
assert event["created_at"] == event["payload"]["observed_at"] == result["consumed_at"]
|
||||||
|
engine.close()
|
||||||
|
engine = Engine(path, clock=lambda: FROZEN+timedelta(days=1), auto_migrate=False)
|
||||||
|
assert engine.get(obj.id).as_dict()["consumption"]["consumed_at"] == result["consumed_at"]
|
||||||
|
retry = engine.consume(obj.id, DIGEST, decision_id="decision:new")
|
||||||
|
assert retry == {**result, "idempotent": True}
|
||||||
|
assert uses(engine) == [event]
|
||||||
|
engine.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("after,accepted", [(450000, True), (750000, False)])
|
||||||
|
def test_consume_samples_after_real_sqlite_lock_wait(tmp_path, after, accepted):
|
||||||
|
current = [FROZEN.replace(microsecond=250000)]
|
||||||
|
path = tmp_path/"approval.sqlite"
|
||||||
|
engine = Engine(path, clock=lambda: current[0])
|
||||||
|
obj = issue(engine, FROZEN-timedelta(seconds=1), FROZEN.replace(microsecond=500000))
|
||||||
|
entered = threading.Event()
|
||||||
|
initialized = threading.Event()
|
||||||
|
start = threading.Event()
|
||||||
|
samples = []
|
||||||
|
def consume():
|
||||||
|
conn = engine._conn()
|
||||||
|
conn.set_trace_callback(lambda statement: entered.set() if statement == "BEGIN IMMEDIATE" else None)
|
||||||
|
initialized.set()
|
||||||
|
assert start.wait(2)
|
||||||
|
def clock():
|
||||||
|
samples.append(current[0])
|
||||||
|
return current[0]
|
||||||
|
engine.clock = clock
|
||||||
|
try:
|
||||||
|
return engine.consume(obj.id, DIGEST, decision_id="decision:original")
|
||||||
|
finally:
|
||||||
|
engine.close()
|
||||||
|
with ThreadPoolExecutor(max_workers=1) as pool:
|
||||||
|
future = pool.submit(consume)
|
||||||
|
assert initialized.wait(2)
|
||||||
|
lock = sqlite3.connect(path)
|
||||||
|
lock.execute("BEGIN IMMEDIATE")
|
||||||
|
start.set()
|
||||||
|
assert entered.wait(2)
|
||||||
|
current[0] = FROZEN.replace(microsecond=after)
|
||||||
|
lock.commit()
|
||||||
|
lock.close()
|
||||||
|
if accepted:
|
||||||
|
assert future.result(timeout=2)["consumed_at"] == current[0].isoformat()
|
||||||
|
else:
|
||||||
|
with pytest.raises(Conflict):
|
||||||
|
future.result(timeout=2)
|
||||||
|
assert samples == [current[0]]
|
||||||
|
assert len(uses(engine)) == int(accepted)
|
||||||
|
engine.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_old_second_precision_receipt_and_payload_are_never_backfilled(tmp_path):
|
||||||
|
engine = Engine(tmp_path/"old.sqlite", clock=lambda: FROZEN)
|
||||||
|
obj = issue(engine, FROZEN-timedelta(seconds=1), FROZEN+timedelta(seconds=1))
|
||||||
|
original = engine.consume(obj.id, DIGEST, decision_id="decision:old")
|
||||||
|
event = uses(engine)[0]
|
||||||
|
assert original["consumed_at"] == "2026-08-29T12:00:00+00:00"
|
||||||
|
engine.clock = lambda: FROZEN + timedelta(days=1, microseconds=123456)
|
||||||
|
assert engine.consume(obj.id, DIGEST, decision_id="decision:new") == {**original, "idempotent": True}
|
||||||
|
assert uses(engine) == [event]
|
||||||
|
|
||||||
|
|
||||||
|
def test_fractional_use_outbox_failure_rolls_back(engine):
|
||||||
|
engine.clock = lambda: FROZEN.replace(microsecond=123456)
|
||||||
|
obj = issue(engine, FROZEN, FROZEN+timedelta(seconds=1))
|
||||||
|
engine.fail_outbox = True
|
||||||
|
with pytest.raises(StoreUnavailable):
|
||||||
|
engine.consume(obj.id, DIGEST)
|
||||||
|
assert engine.get(obj.id).consumed_at is None
|
||||||
|
assert uses(engine) == []
|
||||||
65
workplans/APPROVAL-WP-0006-fractional-consumption-time.md
Normal file
65
workplans/APPROVAL-WP-0006-fractional-consumption-time.md
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
---
|
||||||
|
id: APPROVAL-WP-0006
|
||||||
|
type: workplan
|
||||||
|
title: "Preserve fractional authoritative approval and consumption time"
|
||||||
|
domain: infotech
|
||||||
|
repo: approval-engine
|
||||||
|
status: active
|
||||||
|
flavor: implementation
|
||||||
|
owner: codex
|
||||||
|
topic_slug: netkingdom
|
||||||
|
created: "2026-10-16"
|
||||||
|
updated: "2026-10-16"
|
||||||
|
depends_on: []
|
||||||
|
---
|
||||||
|
|
||||||
|
Explicit CUST-WP-0080 precision defect demand: actual clock .750 was rounded to
|
||||||
|
.000, accepting consume beyond a .500 expiry and recording evidence earlier than
|
||||||
|
the actual signed decision. INTENT basis: valid right now, exact-bound atomic
|
||||||
|
consumption and faithful source evidence. Fix source time, not Access backdating
|
||||||
|
or a consumer waiver. This does not promote native issuer/deployment authority.
|
||||||
|
|
||||||
|
## Preserve clock precision and one post-lock consumption instant
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: APPROVAL-WP-0006-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Preserve aware UTC microseconds through clock/ISO formatting. After acquisition
|
||||||
|
of the SQLite consume lock, sample one time for validity, stored consumption
|
||||||
|
receipt/update and use-event occurrence. Preserve old second-precision rows,
|
||||||
|
original idempotent receipt/decision and local transactional atomicity.
|
||||||
|
|
||||||
|
Seven added regressions: baseline five failed/two passed; repaired 89 focused
|
||||||
|
checks pass. Full source suite: 298 passed, one pre-existing deployment pin vs
|
||||||
|
historical release-evidence mismatch retained. One initial test attempted issue
|
||||||
|
after expiry and was corrected to issue before expiry, then advance the clock;
|
||||||
|
the repaired source correctly refused that expired setup. Source change needs no
|
||||||
|
schema migration and preserves old rows/idempotent timestamps exactly.
|
||||||
|
|
||||||
|
## Review temporal source successor and hand off independently pinned repair
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: APPROVAL-WP-0006-T02
|
||||||
|
status: progress
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Supply exact patch/test/source review evidence for coordinator integration and
|
||||||
|
Access/Secrets immediate strict CAS remeasurement. Review only Approval facts,
|
||||||
|
normalized preapproval correspondence and temporal bounds on the exact successor
|
||||||
|
source. Existing WP0005 closure/R1 review remain historical; native issuer,
|
||||||
|
audit receiver, served-PEP/CAS/effect and cleanup gates remain WP0002 T01/T04/T05
|
||||||
|
and the existing Access/Secrets/Platform records. No native gate is closed here.
|
||||||
|
|
||||||
|
Source owner review and handoff are prepared in
|
||||||
|
`docs/reviews/2026-10-16-fractional-consumption-temporal-approval-owner-review.json`.
|
||||||
|
Standalone repair patch is frozen separately from the Access R3 fact review;
|
||||||
|
89 focused source checks and 298 full checks pass, with one known image-evidence
|
||||||
|
mismatch. Access precise NotBefore remains unchanged and original claim freshness
|
||||||
|
is never renewed. Exact R3 normalized-preapproval/temporal source review covers
|
||||||
|
Approval facts only; native freshness120 configuration is not granted. Independent
|
||||||
|
integration/publication and actual immediate strict CAS successor remeasurement
|
||||||
|
remain pending coordinator/consumer evidence, not a reason to backdate or waive.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue