Problem
provenance.verify_record() now enforces freshness, but its verification time is still taken implicitly from the host clock:
age = time.time() - int(record["issued_at"])
The function accepts decision-relevant freshness policy:
max_age_seconds: int | None = None
max_future_skew_seconds: int = 300
but unlike the main Trust Record verifier and the PIC/TRACE bridge, it has no now= input.
That means a caller cannot deterministically ask the provenance verifier:
evaluate this exact signed record under this exact freshness policy at this exact verification instant.
Boundary reproduction
For a provenance record with:
issued_at = 1000
max_age_seconds = 300
the semantic boundary is clear:
- at verification time
1300: valid under the age bound;
- at verification time
1301: stale.
The sibling TRACE verifier can express that boundary directly because it accepts now:
verify_record(record, key, max_age_seconds=300, now=1300) # pass
verify_record(record, key, max_age_seconds=300, now=1301) # stale
provenance.verify_record() cannot express either call. It always evaluates against the ambient wall clock.
Why this is not just test convenience
The clock value is part of the decision function:
decision = f(record.issued_at, max_age_seconds, max_future_skew_seconds, verification_time)
Changing only verification_time can change the result for the same signed record, trusted key and freshness policy.
So the clock is a decision-relevant environmental input.
Leaving that input implicit has four consequences:
-
Replayability
A retained provenance verification cannot be deterministically rerun at the original decision instant.
-
Conformance / regression tests
Exact freshness-boundary cases require monkeypatching the host clock or constructing timestamps relative to the running process instead of supplying the decision input.
-
Cross-verifier comparison
Two verifiers with identical record, key and policy can disagree solely because they ran at different instants, without an explicit input that explains the divergence.
-
Evidence reconstruction
Recording a timestamp externally after verification is weaker than evaluating against that exact timestamp. The verifier has already sampled time.time() independently.
Relationship to #151
#151 identified that provenance records had issued_at but no freshness or revocation hook. That work is complete: current provenance.verify_record() now accepts max_age_seconds, max_future_skew_seconds and revocation.
This issue is the residual determinism gap in that implementation: freshness exists, but the time input to the freshness calculation is not injectable.
Relationship to #342
#342 is about the semantics of a successful VerificationResult and whether decision-relevant verifier context must be retained for later assurance use.
The maintainer ruling there was that the result is call-local, and callers creating a durable assurance statement should retain the effective freshness bounds and the actual verification time separately.
This issue is one layer earlier:
the provenance verifier currently provides no way to guarantee that the recorded verification time is the exact time used by the freshness calculation.
So this does not propose changing VerificationResult, reopening #342, or adding anything to the signed Trust Record.
Existing repository pattern
The repository already makes verifier time explicit on adjacent decision surfaces:
sign.verify_record(..., now: int | None = None)
intent_bridge.verify_bridge(..., now: int | None = None)
- successor observation evaluation takes explicit
now
- revocation bundle evaluation takes explicit
now
provenance.verify_record() is the freshness-sensitive outlier.
Bounded fix direction
Match the existing verifier pattern:
def verify_record(
...,
max_age_seconds: int | None = None,
max_future_skew_seconds: int = 300,
now: int | None = None,
) -> None:
verification_time = int(time.time()) if now is None else now
age = verification_time - int(record["issued_at"])
The supplied value should use the same validation discipline already applied to verifier-time overrides elsewhere in the package: non-negative integer, with bool refused explicitly.
Regression coverage should include the exact boundary:
now == issued_at + max_age_seconds -> accepted;
now == issued_at + max_age_seconds + 1 -> stale;
- future-skew boundary on both sides;
now=None preserves current wall-clock behavior;
- malformed explicit
now is refused as verifier configuration, not classified as a bad provenance record.
No schema, wire-format or normative Trust Record change appears necessary.
Duplicate check
I searched current TRACE issues for provenance verification-time / now / clock-override / freshness-replay variants.
I found no issue carrying this specific residual input-determinism gap.
AI-assisted FDDCRF source sweep and falsification design; @altrudev remains responsible for the contribution.
Problem
provenance.verify_record()now enforces freshness, but its verification time is still taken implicitly from the host clock:The function accepts decision-relevant freshness policy:
but unlike the main Trust Record verifier and the PIC/TRACE bridge, it has no
now=input.That means a caller cannot deterministically ask the provenance verifier:
Boundary reproduction
For a provenance record with:
the semantic boundary is clear:
1300: valid under the age bound;1301: stale.The sibling TRACE verifier can express that boundary directly because it accepts
now:provenance.verify_record()cannot express either call. It always evaluates against the ambient wall clock.Why this is not just test convenience
The clock value is part of the decision function:
Changing only
verification_timecan change the result for the same signed record, trusted key and freshness policy.So the clock is a decision-relevant environmental input.
Leaving that input implicit has four consequences:
Replayability
A retained provenance verification cannot be deterministically rerun at the original decision instant.
Conformance / regression tests
Exact freshness-boundary cases require monkeypatching the host clock or constructing timestamps relative to the running process instead of supplying the decision input.
Cross-verifier comparison
Two verifiers with identical record, key and policy can disagree solely because they ran at different instants, without an explicit input that explains the divergence.
Evidence reconstruction
Recording a timestamp externally after verification is weaker than evaluating against that exact timestamp. The verifier has already sampled
time.time()independently.Relationship to #151
#151 identified that provenance records had
issued_atbut no freshness or revocation hook. That work is complete: currentprovenance.verify_record()now acceptsmax_age_seconds,max_future_skew_secondsandrevocation.This issue is the residual determinism gap in that implementation: freshness exists, but the time input to the freshness calculation is not injectable.
Relationship to #342
#342 is about the semantics of a successful
VerificationResultand whether decision-relevant verifier context must be retained for later assurance use.The maintainer ruling there was that the result is call-local, and callers creating a durable assurance statement should retain the effective freshness bounds and the actual verification time separately.
This issue is one layer earlier:
So this does not propose changing
VerificationResult, reopening #342, or adding anything to the signed Trust Record.Existing repository pattern
The repository already makes verifier time explicit on adjacent decision surfaces:
sign.verify_record(..., now: int | None = None)intent_bridge.verify_bridge(..., now: int | None = None)nownowprovenance.verify_record()is the freshness-sensitive outlier.Bounded fix direction
Match the existing verifier pattern:
The supplied value should use the same validation discipline already applied to verifier-time overrides elsewhere in the package: non-negative integer, with
boolrefused explicitly.Regression coverage should include the exact boundary:
now == issued_at + max_age_seconds-> accepted;now == issued_at + max_age_seconds + 1-> stale;now=Nonepreserves current wall-clock behavior;nowis refused as verifier configuration, not classified as a bad provenance record.No schema, wire-format or normative Trust Record change appears necessary.
Duplicate check
I searched current TRACE issues for provenance verification-time /
now/ clock-override / freshness-replay variants.I found no issue carrying this specific residual input-determinism gap.
AI-assisted FDDCRF source sweep and falsification design; @altrudev remains responsible for the contribution.