feat(core): add temporal qualifiers and time-aware search (SPEC-82) - #1426
feat(core): add temporal qualifiers and time-aware search (SPEC-82)#1426phernandez wants to merge 1 commit into
Conversation
Observations may carry one authored temporal qualifier naming when a claim
applies in the world, and search gains explicit valid-time filtering over
it. Valid time and recorded time stay separate axes; recorded history waits
for SPEC-59 stable identity rather than shipping a half-version that makes
false historical claims.
Authored forms accept what dateparser reads, canonicalized into one portable
range model:
@effective[2026-06-10,2026-07-27) range literal, agents and precision
@effective:2026-07-27 point, role named
@2026-07-27 point, files on valid
A naive timestamp is read as UTC, matching the house convention rather than
enforcing an offset rule that exists nowhere else in the system. Date-only
input never acquires a time of day. Slash-formatted dates resolve under a new
date_order config setting. The only diagnostic is an unknown role: everything
else either reads as time or stays ordinary content, silently, because direct
file editing is a supported path and does not deserve warnings.
Storage is a portable projection table with scalar bounds, so SQLite and
Postgres share one logical contract and pass the same containment, overlap,
inclusivity, unbounded, and empty-range tests. Rows rebuild on every index
pass and die with the entity. Undated notes are unchanged when no temporal
filter is present, and excluded when one is.
Closes the Phase 1 and Phase 2 MVP of SPEC-82.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp
Signed-off-by: phernandez <paul@basicmachines.co>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 417a355fe4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Unlike PostgreSQL's `daterange`, a discrete date range is not rewritten into the | ||
| canonical `[)` form -- `[a,b]` keeps the inclusivity the author wrote. Evaluating | ||
| the authored flags directly is set-equivalent for containment and overlap and needs | ||
| no date arithmetic; only the rendered literal differs. |
There was a problem hiding this comment.
Normalize discrete date ranges before overlap checks
For date ranges, preserving the authored bounds is not set-equivalent for overlap because calendar dates form a discrete domain. For example, (2026-01-01,2026-01-03) contains only January 2, while (2026-01-02,2026-01-04) contains only January 3, yet the scalar predicate reports them as overlapping because each raw endpoint lies inside the other's bounds. Normalize date bounds to an equivalent half-open interval using day arithmetic, or make the overlap predicate account for the successor date, so valid_overlaps does not return sources sharing no calendar date.
Useful? React with 👍 / 👎.
What
SPEC-82 Phases 1+2. Observations may carry one authored temporal qualifier saying when a claim applies in the world, and search gains explicit valid-time filtering over it. Recorded-time history is deliberately not implemented — it depends on SPEC-59 stable identity, and a half-version would make false historical claims.
search("cache layer", role=effective, valid_at=2026-07-28)returns only Memcached; at2026-07-01only Redis; with no temporal filter both compete under ordinary relevance, exactly as today.Authored forms
Role is optional in both; omitted, it files on
valid. The bracket form needs no separator (no role name starts with[/(); the point form needs:because a date can begin with a letter (yesterday).Accepts whatever
dateparserreads — already a dependency, already used for frontmatter dates atentity_parser.py:198. Precision is the meaning: a year is the year, a month is the month, a date is from that date onward.Decisions that changed during the build
Three product calls landed mid-implementation and were applied afterward (decision note):
utils.py,recent_activity.py). A rule enforced by one feature and nowhere else is an inconsistency, not a principle. Replaces acceptance criterion 10 with its inverse.date_orderconfig (YMDdefault) so slash-formatted dates resolve by documented preference rather than silent guess.Storage
Portable projection table with scalar bounds and inclusivity flags, so SQLite and PostgreSQL share one logical contract and pass the same containment, overlap, inclusivity, unbounded, and empty-range tests. Rows rebuild on every index pass and die with the entity — a derived projection, never canonical. Native PostgreSQL range columns remain a later optimization generated from these columns.
Guardrails worth noting
mayas May andv2as February; a bare@wordat the head of an observation is a mention or a version, and accepting it would file wrong valid time onto ordinary prose. Relative dates still work with an explicit role (@occurred:yesterday).Verification
ruff, format,
ty— clean. Full unit suite 6,222 passed, 41 skipped (real captured exit codes). 100% coverage ontemporal.py(178/178),temporal_qualifier.py(53/53),temporal_filters.py(38/38). The migration docstring's claim that full-text always drives temporal queries was corrected —has_temporal_filter()counts as criteria, so temporal-only searches are valid.Open question for review
Point precision currently means: year → the year, month → the month, date → from that date onward. That is right for
effective/valid(a state that begins and persists) but arguably wrong foroccurred/due(an event at a time) —@occurred:2026-06-10currently means "occurred from June 10 onward," so a query for June 20 matches it. Making the interpretation role-dependent (state roles open-ended, event roles bounded by the written precision) would resolve it. Flagged rather than decided, and it matters for the Moby Dick eval, which authors@occurredheavily.🤖 Generated with Claude Code
https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp