Skip to content

feat: preserve event time separately from extraction time in ClaimEvent #717

Open

Description

@cdeust

Problem

ClaimEvent contains extracted_at but no dedicated event-occurrence or validity interval fields. This specific contract cannot preserve that distinction structurally; Cortex already has other temporal mechanisms, so this is not a claim that temporal retrieval is absent.

Proposed solution

Define event occurrence and, where needed, validity intervals with explicit unknowns, precision and timezone. Carry supported fields through extraction, persistence, synthesis and retrieval without converting an extraction timestamp into an event timestamp.

Acceptance evidence

  • A historical event extracted today retains its historical occurrence separately from extracted_at.
  • Unknown, point and interval dates round-trip with their precision and timezone.
  • Corrections and contradictory dates preserve provenance and uncertainty; do not invent a date.

Alternatives considered

Keep the current behavior and document its limits. This remains valid where the added guarantee has no user need; the issue should close with a documented product decision rather than assume that copying Hindsight is always appropriate. Reuse existing Cortex mechanisms before adding a second pipeline.

Scope assessment

MEDIUM. Raised from the October 10 Hindsight/Cortex comparison. Static observations refer to the pinned revisions below. They do not establish successful execution of Hindsight's whole pipeline or a repository-wide absence in Cortex. Priority is a proposal, not a release blocker.

Evidence

mcp_server/shared/wiki_ir.py:28; comparator hindsight-api-slim/hindsight_api/api/http.py:6247

Activity

  1. cdeust commented on Nov 5, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : première tranche livrée dans #875 (Refs #717, pas Closes).

    Tranche 1 : le contrat. ClaimEvent.event_time (EventTime : unknown / point / interval, chaque borne avec sa valeur, sa précision ISO 8601 et, si la source l'énonce, son décalage horaire). Valeur par défaut unknown, jamais dérivée de extracted_at. Les validateurs refusent une précision inventée, des bornes contraires au type, un intervalle inversé ou mêlant bornes avec et sans zone. 18 tests ajoutés, 825 tests voisins passent ; suite complète et pyright non lancés ici (pile torch non installable dans cet environnement), la CI fera foi.

    Tranches restantes :

    1. Persister event_time dans wiki.claim_events (PostgreSQL et SQLite), migration unique.
    2. Extraire les dates énoncées dans core/claim_extractor via shared/temporal_normalize, avec leur précision, sans repli sur extracted_at.
    3. Porter le champ à travers la synthèse et la recherche ; corrections et dates contradictoires conservent provenance et incertitude.

    Generated by Claude Code

  2. cdeust commented on Nov 5, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : tranche 2 dans #894 (Refs #717, pas Closes).

    Tranche 2 : la persistance. wiki.claim_events.event_time contient le JSON d'EventTime (JSONB sous PostgreSQL, JSON sous SQLite). Une borne sans zone reste sans zone, une borne avec zone garde son décalage, ce qu'une paire TIMESTAMPTZ aurait ramené en UTC. Les bases existantes migrent une fois et leurs lignes se lisent unknown. wiki_extract écrit le champ. 4 tests SQLite passent (ils échouent sur main) ; les variantes PostgreSQL et la suite complète n'ont pas pu tourner ici (pas de pgvector, torch non téléchargeable), la CI fera foi.

    Tranches restantes :

    1. Extraire les dates énoncées dans core/claim_extractor via shared/temporal_normalize, avec leur précision, sans repli sur extracted_at.
    2. Porter le champ à travers la synthèse et la recherche ; corrections et dates contradictoires gardent leur provenance et leur incertitude.

    Generated by Claude Code

  3. cdeust commented on Nov 6, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : la tranche 3 est dans #923 (Refs #717, pas Closes).

    Tranche 3, l'extraction. extract_claims remplit maintenant event_time avec la date que la phrase de la claim énonce, à la précision où elle est écrite. « March 2026 » donne un mois. « 12 March 2026 » et « March 12, 2026 » donnent un jour. Les formats ISO 8601 étendus donnent un mois, un jour, une minute ou une seconde, avec leur décalage s'il est indiqué (sinon la valeur reste naïve). Ces cas restent unknown : pas de date, une année seule, une date relative, une date ou un décalage impossible, des fractions de seconde, ou deux dates différentes dans la même phrase. La valeur n'est jamais prise dans extracted_at.

    Je n'ai pas réutilisé normalize_date_to_iso : il complète les composants absents. Mesuré le 2026-11-06 : « March 2026 » donne 2026-03-06, et « 2019 » donne 2019-11-06.

    Ce que j'ai vérifié : les 35 nouveaux tests passent, ainsi que 1321 tests voisins (25 ignorés). La porte de mutation sur les lignes modifiées donne 0 échec, et la porte craftsmanship est OK.

    Pas vérifié : la suite complète, pyright et les variantes PostgreSQL (torch ne se télécharge pas dans cet environnement). La CI fera foi.

    Tranche restante : porter le champ à travers la synthèse et la recherche. Les corrections et les dates contradictoires doivent y garder leur provenance et leur incertitude. Les intervalles (« from X to Y ») en font partie.


    Generated by Claude Code

  4. cdeust commented on Nov 7, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : tranche 4 dans #926 (Refs #717, pas Closes ; commit 8d17252).

    Tranche 4, les intervalles. stated_event_time lit maintenant « from D1 to|until|till|through D2 » et « between D1 and D2 » comme un intervalle fermé. « since D » donne un début avec une fin inconnue, « until|till D » une fin avec un début inconnu. Chaque borne garde sa précision et son fuseau écrits.

    Restent unknown : les intervalles abrégés (« from 10 to 12 March 2026 », « 10-12 March 2026 »), parce que copier le mois et l'année de la seconde borne inventerait ce que la phrase ne dit pas. Restent aussi unknown une fin avant le début et une paire où une seule borne indique un fuseau.

    Ce que j'ai vérifié : 75 tests du module passent, ainsi que 140 tests voisins (1 ignoré). La porte de mutation locale donne 203 mutants tués sur 204 ; le survivant est enregistré comme équivalent par construction dans memory/mutation-equivalents.json. ruff et la porte craftsmanship sont OK.

    Pas vérifié : la suite complète, pyright et les variantes PostgreSQL (torch ne se télécharge pas dans cet environnement). La CI fera foi.

    Tranches restantes :

    1. Recherche : wiki_view ne peut ni sélectionner ni filtrer event_time sur claim_events. Avant de coder, il faut une décision : comment comparer des bornes de précisions différentes (un mois contre un jour) et des valeurs naïves contre des valeurs avec fuseau.
    2. Synthèse : les brouillons référencent leurs claims par claim_ids, donc event_time reste joignable. _claims_for_memory ne le lit pas encore, et wiki_frontmatter n'accepte pas de dictionnaire imbriqué.
    3. Corrections et dates contradictoires (critère 3) : chaque claim garde son propre event_time et supersedes, mais aucun test ne le prouve encore de bout en bout.
    4. Intervalles abrégés, si on décide de les lire.

    Generated by Claude Code

  5. cdeust commented on Nov 7, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : tranche 5 dans #927 (Refs #717, pas Closes ; commit b308854).

    Tranche 5, critère 3 (corrections et dates contradictoires). Un test bout-en-bout wiki_extract → wiki_resolve sur SQLite prouve que la résolution écrit supersedes et des memos sans toucher event_time : la claim remplacée garde sa date plus ancienne, deux dates contradictoires pour la même décision survivent avec leur précision sous un memo conflict_candidate, et une claim sans date reste unknown. Aucun code de production modifié : le comportement était correct mais non prouvé.

    Vérifié : 5 tests nouveaux et 172 tests voisins passent (4 ignorés, PostgreSQL absent). Une mutation qui fait réinitialiser event_time par update_claim_supersedes fait échouer 2 des 5 tests. ruff et la porte craftsmanship sont OK.

    Pas vérifié : la suite complète, pyright, PostgreSQL (torch ne se télécharge pas ici). La CI fera foi.

    Observation hors périmètre : le résolveur ordonne la supersession par extracted_at, pas par event_time. Une correction extraite avant la claim qu'elle corrige ne la remplacerait pas.

    Tranches restantes :

    1. Recherche : wiki_view sur event_time (décision à prendre sur la comparaison de bornes de précisions et de fuseaux différents).
    2. Synthèse : _claims_for_memory ne lit pas event_time, wiki_frontmatter n'accepte pas de dictionnaire imbriqué.
    3. Intervalles abrégés, si on décide de les lire.

    Generated by Claude Code

  6. cdeust commented on Nov 7, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : en attente d'une décision (autopilot:hold).

    Les tranches 1 à 5 sont fusionnées (dernières : #926, #927). Les trois critères d'acceptation ont chacun un test : date historique distincte de extracted_at, aller-retour point/intervalle/inconnu avec précision et fuseau, corrections et dates contradictoires sans date inventée. Les trois tranches restantes demandent chacune un choix que le code ne peut pas faire seul :

    1. Synthèse (wiki_synthesize._claims_for_memory, draft_synthesizer) : un brouillon agrège plusieurs claims, chacune avec son propre event_time. Quelle date la page porte-t-elle ? Options : (a) aucune au niveau page, event_time par claim dans la provenance des sections ; (b) une date de page seulement si toutes les claims datées ont le même event_time, sinon unknown ; (c) l'enveloppe min/max des bornes, ce qui exige de comparer des précisions différentes (mois contre jour). (b) et (c) imposent aussi un frontmatter imbriqué, que shared/wiki_frontmatter._format_frontmatter refuse aujourd'hui (« keep metadata flat »).
    2. Recherche (wiki_view sur event_time) : comment comparer « March 2026 » à « 2026-03-12 » ou une borne naïve à une borne avec décalage (recouvrement, inclusion, ou exclusion des bornes incomparables) ?
    3. Intervalles abrégés (« 10-12 March 2026 ») : les lire en copiant mois et année de la seconde borne, ou les laisser unknown comme aujourd'hui ?

    Une réponse sur l'un de ces points (ou la décision de fermer l'issue avec la limite documentée, comme le prévoit sa section Alternatives considered) suffit à relancer l'autopilot.


    Generated by Claude Code

  7. added
    autopilot:holdCI autopilot stood down: a human decision is required
    on Nov 7, 2026
  8. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    Décision : cette issue n'attend plus de réponse du propriétaire.

    Le propriétaire a demandé le 12 novembre 2026 que les issues avancent sans lui ; il ne répondra pas aux questions posées ici. Ce commentaire de cdeust lève le hold et précise la demande. Il vaut pour toute la suite de cette issue.

    • Tranche toi-même chaque question restée ouverte : d'abord une décision écrite et acceptée du dépôt (ADR accepté, docs/), puis ce que le texte de l'issue propose ou exclut, puis les standards du dépôt ; à défaut, la plus petite tranche qui réalise ce que l'issue demande. Une valeur qui manque se mesure, elle ne s'invente pas.
    • Écris la décision dans un commentaire marqué <!-- ci-autopilot decision --> (ce qui est retenu, la raison et sa source, ce qui est écarté, comment revenir en arrière), puis livre la tranche dans une PR. Une décision écrite ne se rejoue pas aux passages suivants.
    • Ne repose pas autopilot:hold pour une question : seulement pour l'irréversible, un accès que tu n'as pas, une dépendance nommée encore ouverte ou le plafond de tentatives, en nommant exactement ce qui manque.
    • Jamais de tag, de release, de montée de version du projet ni de déclenchement manuel d'un workflow.

    Règle complète : #343 (comment)

    Écrit par la session « écosystème autonome » sur instruction du propriétaire (12 novembre 2026).


    Generated by Claude Code

  9. removed
    autopilot:holdCI autopilot stood down: a human decision is required
    on Nov 12, 2026
  10. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    Hold autopilot:hold levé : un commentaire de cdeust (« Décision : cette issue n'attend plus de réponse du propriétaire », 12/11/2026) a été posté après la pose du hold.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions