docs(6205): backup comments state the reason, not the instance that taught it
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 1m11s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 22s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 1m11s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 22s
The format-version log keeps what each version adds, because restore depends on what an older archive can carry; dates, milestone and issue ids, and the stories of what a missing column once cost are removed. Compatibility comments now name the archive version instead of a milestone. Two leftover history-phrased comments in embeddings and shape_ledger are restated as present-tense reasons. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+188
-225
@@ -42,92 +42,75 @@ from scribe.models.user_profile import UserProfile
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Backup format version. v3 (2026-06) added rulebooks/topics/rules + their
|
||||
# project subscription/suppression join tables. v4 (2026-07) dropped events
|
||||
# when the calendar surface was retired — old v3 events are skipped on restore.
|
||||
# v5 (2026-08) added the six tables that had accumulated outside the backup
|
||||
# entirely (#2293), and the coverage guard that stops the seventh.
|
||||
# v6 (2026-08) added note_supersessions — and the guard did stop the seventh:
|
||||
# the table shipped without a backup section and the coverage test failed the
|
||||
# build, which is the whole reason that list was written.
|
||||
# v7 (2026-08) added code_shapes — the shape ledger (#2787). Classifications
|
||||
# are judgment data worth carrying; a restore keeps a judgment only when its
|
||||
# snippet target survives the id re-mapping, else the row rejoins the todo.
|
||||
# v8 (2026-08) added code_shape_events — the ledger's history (#2793): what
|
||||
# was used where, when, and why is not recomputable, so it travels.
|
||||
# v9 (2026-08) added code_shape_uses — the ledger's consumption edges (#2870):
|
||||
# judgment-grade edges (agent/audit/import) are operator records; mechanical
|
||||
# ones (reference/hook) travel too, cheaply, and the next refresh refreshes them.
|
||||
# v10 (2026-08) added projects.inception + project_rulebook_exclusions
|
||||
# (milestone 297): the WHY a project inherits what it does, and its opt-outs.
|
||||
# v11 (2026-08) added the note verification trio — notes.verify_with /
|
||||
# expires_when / verified_at (milestone 317).
|
||||
# v12 (2026-08) closed #3182: the nine Note columns that had been missing for
|
||||
# years (note_type, task_kind, arose_from_id, data, description, the
|
||||
# recurrence pair, started_at, completed_at), plus milestones.body — which IS
|
||||
# the plan — and repo_bindings.ref. Until v12 a restore reported success and
|
||||
# handed back a corpus with every snippet and process flattened into a plain
|
||||
# note, every issue and spike into `work`, and every plan reduced to a title.
|
||||
# _COLUMN_EXCLUSIONS and its guard landed with it, so the next such column
|
||||
# fails the build instead.
|
||||
# v13 (2026-08) added rule_versions — a rule's edit history (milestone 323).
|
||||
# v14 (2026-09) added rule_usage_events — the rule twin of note_usage_events
|
||||
# (milestone 333). Carrying it is the WHOLE REASON the table is separate: the
|
||||
# note importer maps note_id through note_id_map, so a rule id parked there
|
||||
# would restore attached to whatever note took that number.
|
||||
# v15 (2026-09) dropped rulebook_subscriptions / rule_suppressions /
|
||||
# topic_suppressions with their tables (milestone 414): a rule's scope is its
|
||||
# home now. Older archives carrying those sections still restore — the keys are
|
||||
# simply not read — as do the subscribe_rulebooks inception choices they hold.
|
||||
# v16 (2026-09) added retrieval_tuning_events (milestone 416): the REASON each
|
||||
# retrieval floor and budget is where it is. `settings` already carried the
|
||||
# numbers, so leaving this behind would restore six moved dials with the
|
||||
# argument for them silently dropped — and from this step on those dials are
|
||||
# moved by the model, which is exactly the case where the operator needs the
|
||||
# argument to review.
|
||||
# v17 (2026-09) added retrieval_tuning_events.embedding_model / shape_version
|
||||
# (milestone 416 step 6): a floor is a distance in ONE embedding model's
|
||||
# geometry over documents cut one particular way, so the number alone cannot
|
||||
# say whether it still measures anything. Both travel NULLABLE and unfilled —
|
||||
# a row written before the stamp existed restores unstamped, because inventing
|
||||
# the model it was measured under would turn "unknown" into a stated fact.
|
||||
# v18 (2026-10) added lesson_rule_links (milestone 440): which rule each lesson
|
||||
# is an instance of, and the judgments that confirmed or rejected each pair. A
|
||||
# confirmed link is a judgment nothing else records, and a rejected one is
|
||||
# what stops the pair being proposed again — losing either undoes work.
|
||||
# v19 (2026-10) added lesson_no_rule (#4631): the "no rule fits" answer and its
|
||||
# reason. Without it a restored lesson that was judged to stand alone reads as
|
||||
# never judged, and lands back on the unjudged list.
|
||||
# v20 (2026-10) added systems.path_patterns and system_usage_events (milestone
|
||||
# 444): the files that are each area, and whether an area's rulings were read
|
||||
# once shown. The usage rows restore through the SYSTEM map, for the reason
|
||||
# the rule twin restores through the rule map.
|
||||
# v21 (2026-10) added moment_mappings (milestone 458): which of this install's
|
||||
# actions reach which moment, and the shipped defaults it switched off. Each
|
||||
# row is a correction an operator made in-session; a restore that dropped them
|
||||
# would silently put every misfire back.
|
||||
# v22 (2026-10) added rule_moments (milestone 458): the moments each rule
|
||||
# arrives at. A mount is a judgment about when a rule applies that nothing in
|
||||
# the rule's text records, and losing it would put back exactly the misses the
|
||||
# mounts were made to fix.
|
||||
# v23 (2026-10) added rule_moment_judgments (milestone 458 step 7): the
|
||||
# proposals waiting on a mount, and the rejections and "no moment fits"
|
||||
# answers that stop a pass or the open-after-moment signal proposing the same
|
||||
# pair again. Losing them puts every answered question back on the list.
|
||||
# v24 (2026-10) added rule_moment_judgments.misfire (milestone 458 step 7b):
|
||||
# the reports that a mounted rule arrived where it did not apply, and the
|
||||
# operator's "keep it" that stops them being proposed as an unmount again.
|
||||
# v25 (2026-10) added family canon (milestone 463): the platform catalog, each
|
||||
# project's platforms, family ideas with their platforms and reference
|
||||
# implementations, the adoption ledger and the decision log. The ledger is
|
||||
# every project's recorded answer to a shared idea, and the log is the
|
||||
# precedent each later answer follows. Lose either and every assessment is
|
||||
# owed again, made fresh, with nothing to keep it consistent with the last.
|
||||
# v26 (2026-10) added user_profiles (#5879): the Profile page — name, role,
|
||||
# expertise, response style, tone, interests, working hours. Every field is
|
||||
# typed by its user and none is derived or a credential, so leaving it out made
|
||||
# a restore drop it without a word.
|
||||
# Bump when the serialized schema changes.
|
||||
# Backup format version. Bump when the serialized schema changes. What each
|
||||
# version added, so a restore knows what an older archive can carry:
|
||||
#
|
||||
# v3 rulebooks, topics and rules.
|
||||
# v4 drops calendar events; a v3 archive's events are skipped on restore.
|
||||
# v5 systems, record_systems, design systems and tokens, note usage events,
|
||||
# repo bindings — and the coverage guard below.
|
||||
# v6 note_supersessions.
|
||||
# v7 code_shapes, the shape ledger. A restore keeps a classification only
|
||||
# when its snippet target survives the id re-mapping, else the row rejoins
|
||||
# the todo.
|
||||
# v8 code_shape_events, the ledger's history: what was used where, when and
|
||||
# why is not recomputable, so it travels.
|
||||
# v9 code_shape_uses, the ledger's consumption edges. Judgment-grade edges
|
||||
# (agent/audit/import) are operator records; mechanical ones
|
||||
# (reference/hook) travel too, cheaply, and the next refresh refreshes
|
||||
# them.
|
||||
# v10 projects.inception and project_rulebook_exclusions: why a project
|
||||
# inherits what it does, and its opt-outs.
|
||||
# v11 the note verification trio — verify_with / expires_when / verified_at.
|
||||
# v12 the Note typing and provenance columns (note_type, task_kind,
|
||||
# arose_from_id, data, description, the recurrence pair, started_at,
|
||||
# completed_at), milestones.body — which IS the plan — and
|
||||
# repo_bindings.ref; and the column guard below. An archive older than
|
||||
# v12 restores every snippet and process as a plain note, every issue and
|
||||
# spike as `work`, and every plan as a title.
|
||||
# v13 rule_versions, a rule's edit history.
|
||||
# v14 rule_usage_events, the rule twin of note_usage_events. Carrying it is the
|
||||
# WHOLE REASON the table is separate: the note importer maps note_id
|
||||
# through note_id_map, so a rule id parked there would restore attached to
|
||||
# whatever note took that number.
|
||||
# v15 drops the rule subscription and suppression tables: a rule's scope is
|
||||
# its home. Older archives carrying those sections still restore — the
|
||||
# keys are simply not read.
|
||||
# v16 retrieval_tuning_events: the REASON each retrieval floor and budget is
|
||||
# where it is. `settings` carries the numbers; without this a restore
|
||||
# keeps moved dials and drops the argument for them, which an operator
|
||||
# needs to review a dial the model moved.
|
||||
# v17 retrieval_tuning_events.embedding_model / shape_version: a floor is a
|
||||
# distance in ONE embedding model's geometry over documents cut one
|
||||
# particular way. Both travel NULLABLE — an unstamped row restores
|
||||
# unstamped, because inventing the model it was measured under would turn
|
||||
# "unknown" into a stated fact.
|
||||
# v18 lesson_rule_links: which rule each lesson is an instance of, and the
|
||||
# judgments on each pair. A confirmed link is a judgment nothing else
|
||||
# records, and a rejected one stops the pair being proposed again.
|
||||
# v19 lesson_no_rule: the "no rule fits" answer and its reason. Without it a
|
||||
# lesson judged to stand alone lands back on the unjudged list.
|
||||
# v20 systems.path_patterns and system_usage_events. The usage rows restore
|
||||
# through the SYSTEM map, for the reason the rule twin restores through the
|
||||
# rule map.
|
||||
# v21 moment_mappings: which of this install's actions reach which moment, and
|
||||
# the shipped defaults it switched off. Each row is a correction an
|
||||
# operator made; dropping them puts every misfire back.
|
||||
# v22 rule_moments: the moments each rule arrives at — a judgment about when a
|
||||
# rule applies that nothing in the rule's text records.
|
||||
# v23 rule_moment_judgments: proposals waiting on a mount, and the rejections
|
||||
# and "no moment fits" answers that stop the same pair being proposed
|
||||
# again.
|
||||
# v24 rule_moment_judgments.misfire: reports that a mounted rule arrived where
|
||||
# it did not apply, and the operator's "keep it" that stops them being
|
||||
# proposed as an unmount again.
|
||||
# v25 family canon: the platform catalog, each project's platforms, family
|
||||
# ideas with their platforms and reference implementations, the adoption
|
||||
# ledger and the decision log. Lose the ledger or the log and every
|
||||
# assessment is owed again, with nothing to keep it consistent with the
|
||||
# last.
|
||||
# v26 user_profiles: what each user wrote on their Profile page. Every field
|
||||
# is typed by its user, and none is derived or a credential.
|
||||
BACKUP_VERSION = 26
|
||||
|
||||
# Every table this backup carries, by its REAL name. Paired with _NOT_INCLUDED
|
||||
@@ -137,55 +120,49 @@ BACKUP_VERSION = 26
|
||||
# The point is the ABSENCE case. A new table gets a model and a migration, both
|
||||
# of which fail loudly if wrong, and then silently never gets a backup section:
|
||||
# no error, no warning, and a restore that reports success. Naming the coverage
|
||||
# explicitly turns "someone forgot" into a failing test (#2293).
|
||||
# explicitly turns "someone forgot" into a failing test.
|
||||
_BACKED_UP = [
|
||||
"users", "projects", "milestones", "notes", "task_logs", "note_drafts",
|
||||
"note_versions", "settings", "rulebooks", "rulebook_topics", "rules",
|
||||
# v5 (2026-08): the five-year gap this list was written to stop.
|
||||
"systems", "record_systems", "design_systems", "design_tokens",
|
||||
"note_usage_events", "repo_bindings", "note_supersessions",
|
||||
# v7 (2026-08): the shape ledger (#2787); v8: its history (#2793).
|
||||
# The shape ledger, its history and its consumption edges.
|
||||
"code_shapes", "code_shape_events", "code_shape_uses",
|
||||
# v9 (2026-08): the global area catalog (milestone 307). Global, not
|
||||
# The global area catalog. Global, not
|
||||
# user-scoped, so it rides in EVERY export — including a single-user
|
||||
# one, whose Systems would otherwise restore unmapped.
|
||||
"canonical_systems",
|
||||
# v10 (2026-08): a rule's area tag and its typed edges (milestone 307).
|
||||
# A rule's area tag and its typed edges.
|
||||
"rule_systems", "rule_relations",
|
||||
# v13 (2026-08): a rule's edit history (milestone 323). note_versions has
|
||||
# always travelled; its sibling has no excuse not to.
|
||||
# A rule's edit history, for the reason note_versions travels.
|
||||
"rule_versions",
|
||||
# v14 (2026-09): rule usage telemetry (milestone 333). Same argument
|
||||
# Rule usage telemetry. Same argument
|
||||
# note_usage_events makes for itself — pull-through is only ever
|
||||
# accumulated, so a restore that dropped it would silently reset the
|
||||
# measurement to zero while everything still looked fine.
|
||||
"rule_usage_events",
|
||||
# v16 (2026-09): why each retrieval floor and budget is where it is
|
||||
# (milestone 416). The values live in `settings` and already travelled; the
|
||||
# argument for them had nowhere to go. Now that the model moves these dials
|
||||
# on the operator's behalf, a restore that kept the numbers and dropped the
|
||||
# reasons would leave an install tuned by nobody it can name.
|
||||
# Why each retrieval floor and budget is where it is. The values live in
|
||||
# `settings`; the model moves these dials on the operator's behalf, so a
|
||||
# restore that kept the numbers and dropped the reasons would leave an
|
||||
# install tuned by nobody it can name.
|
||||
"retrieval_tuning_events",
|
||||
# v18 (2026-10): lesson → rule links and their judgments (milestone 440).
|
||||
# Lesson → rule links and their judgments, and "no rule fits" answers.
|
||||
"lesson_rule_links",
|
||||
# v19 (2026-10): "no rule fits" answers (#4631).
|
||||
"lesson_no_rule",
|
||||
# v20 (2026-10): System usage telemetry (milestone 444), for the reason
|
||||
# its note and rule twins travel.
|
||||
# System usage telemetry, for the reason its note and rule twins travel.
|
||||
"system_usage_events",
|
||||
# v21 (2026-10): an install's action → moment corrections (milestone 458).
|
||||
# An install's action → moment corrections, the moments each rule arrives
|
||||
# at, and the proposals and judgments about those mounts.
|
||||
"moment_mappings",
|
||||
# v22 (2026-10): the moments each rule arrives at (milestone 458).
|
||||
"rule_moments",
|
||||
# v23 (2026-10): proposals and judgments about those mounts (step 7).
|
||||
"rule_moment_judgments",
|
||||
# v25 (2026-10): family canon (milestone 463). `platforms` is global like
|
||||
# Family canon. `platforms` is global like
|
||||
# canonical_systems and rides every export for the same reason: the rows
|
||||
# below name platforms by slug, and a partial catalog restores partial
|
||||
# memberships.
|
||||
"platforms", "project_platforms", "family_ideas", "family_idea_platforms",
|
||||
"family_idea_references", "family_adoptions", "family_decisions",
|
||||
# v26 (2026-10): what each user wrote on their Profile page (#5879).
|
||||
# What each user wrote on their Profile page.
|
||||
"user_profiles",
|
||||
]
|
||||
|
||||
@@ -197,9 +174,8 @@ _BACKED_UP = [
|
||||
# reads for correctness and that grows per query; the rest are
|
||||
# transient/operational.
|
||||
#
|
||||
# REAL table names, deliberately. This list used to read "embeddings",
|
||||
# "invitations", "password_resets" — none of which are tables — so it looked
|
||||
# like coverage while naming nothing the schema could confirm.
|
||||
# REAL table names, deliberately: a name that is not a table looks like
|
||||
# coverage while naming nothing the schema can confirm.
|
||||
_NOT_INCLUDED = [
|
||||
"groups", "group_memberships", "project_shares", "note_shares",
|
||||
"api_keys", "note_embeddings", "rule_embeddings", "milestone_embeddings",
|
||||
@@ -207,7 +183,7 @@ _NOT_INCLUDED = [
|
||||
"app_logs", "notifications",
|
||||
"invitation_tokens", "password_reset_tokens",
|
||||
"retrieval_logs",
|
||||
# Verdicts on retrieval_logs rows (#4772), so they go where their subject
|
||||
# Verdicts on retrieval_logs rows, so they go where their subject
|
||||
# goes: a restore that carried a judgment of a call it did not carry would
|
||||
# leave a verdict on nothing. A sample, re-takeable by the review pass.
|
||||
"retrieval_judgments",
|
||||
@@ -215,9 +191,9 @@ _NOT_INCLUDED = [
|
||||
# forge tokens is a token-exfiltration file. Users re-add connections
|
||||
# after a restore; the per-project pin (projects.forge_connection_id) is
|
||||
# deliberately not exported either, so restored projects fall back to
|
||||
# keyring-by-host resolution — the documented unpinned behavior (#2778).
|
||||
# keyring-by-host resolution — the documented unpinned behavior.
|
||||
"forge_connections",
|
||||
# Derived, like note_embeddings: the CSS consumer map (milestone 302) is
|
||||
# Derived, like note_embeddings: the CSS consumer map is
|
||||
# rebuilt from the repo archive by every coverage sync, and carries no
|
||||
# judgment — the first refresh after a restore recreates it exactly.
|
||||
"code_shape_consumers",
|
||||
@@ -226,20 +202,14 @@ _NOT_INCLUDED = [
|
||||
|
||||
# Columns a backed-up table deliberately does NOT export, per table. Paired
|
||||
# with the column-coverage guard in tests/test_services_backup.py, this is
|
||||
# _NOT_INCLUDED's shape one level down — and it exists because the table guard
|
||||
# could not see the failure it was written to stop.
|
||||
# _NOT_INCLUDED's shape one level down. The table guard cannot see a column
|
||||
# missing from a table it counts as covered — note_type dropped would restore
|
||||
# every snippet as a plain note; milestones.body dropped would restore every
|
||||
# plan as a title. A column added to the model and the migration fails loudly
|
||||
# if wrong; one never added to the serialiser fails silently.
|
||||
#
|
||||
# #2293 was six whole tables missing. #3182 was NINE COLUMNS missing from a
|
||||
# table that had been "covered" for years: note_type and task_kind, so every
|
||||
# snippet and process restored as a plain note and every issue and spike as
|
||||
# `work`; arose_from_id, so every provenance edge vanished; the recurrence
|
||||
# pair, so recurring tasks stopped recurring; milestones.body, which IS the
|
||||
# plan; repo_bindings.ref, the branch a ledger follows. Every one arrived the
|
||||
# same way — a column added to the model and the migration, both of which fail
|
||||
# loudly, and then never added to the serialiser, which fails silently.
|
||||
#
|
||||
# So: a new column on a backed-up table now fails the build unless it is either
|
||||
# exported or named here with a reason. "I forgot" is no longer expressible.
|
||||
# So a new column on a backed-up table fails the build unless it is either
|
||||
# exported or named here with a reason. "I forgot" is not expressible.
|
||||
_COLUMN_EXCLUSIONS: dict[str, set[str]] = {
|
||||
# Trash is not exported at all, so neither is the batch id that groups a
|
||||
# deletion for restore(). Uniform across every soft-deletable table.
|
||||
@@ -248,14 +218,14 @@ _COLUMN_EXCLUSIONS: dict[str, set[str]] = {
|
||||
"deleted_at", "deleted_batch_id",
|
||||
# Credentials-adjacent, same reasoning as api_keys and
|
||||
# forge_connections: a restored project falls back to keyring-by-host
|
||||
# resolution, which is the documented unpinned behaviour (#2778).
|
||||
# resolution, which is the documented unpinned behaviour.
|
||||
"forge_connection_id",
|
||||
},
|
||||
"milestones": {"deleted_at", "deleted_batch_id"},
|
||||
"notes": {
|
||||
"deleted_at", "deleted_batch_id",
|
||||
# A claim is a session's attention, not the work's state (milestone
|
||||
# 381). Restoring one would assert that a session on another install,
|
||||
# A claim is a session's attention, not the work's state. Restoring
|
||||
# one would assert that a session on another install,
|
||||
# possibly long gone, is working the task right now.
|
||||
"claimed_by", "claimed_at", "claim_touched_at", "claim_session",
|
||||
},
|
||||
@@ -339,7 +309,7 @@ _COLUMN_EXCLUSIONS: dict[str, set[str]] = {
|
||||
}
|
||||
|
||||
|
||||
# The same accounting for the way BACK IN (#4197). `_COLUMN_EXCLUSIONS` above
|
||||
# The same accounting for the way BACK IN. `_COLUMN_EXCLUSIONS` above
|
||||
# makes a dropped column unexpressible on the way out; this one does it for the
|
||||
# import, where the same mistake is worse — an export gap leaves an obviously
|
||||
# thin backup, an import gap means holding a complete, correct file and
|
||||
@@ -553,7 +523,7 @@ def _rule_usage_event_rows(rows) -> list[dict]:
|
||||
{
|
||||
"user_id": r.user_id, "rule_id": r.rule_id, "event": r.event,
|
||||
"source": r.source,
|
||||
# The reason a rule was departed from (#4212). Exported because
|
||||
# The reason a rule was departed from. Exported because
|
||||
# it is the only field on this table that cannot be recomputed:
|
||||
# counts can be re-derived from a fresh install's own use, a
|
||||
# stated reason cannot, and a `departed` row that comes back
|
||||
@@ -566,7 +536,7 @@ def _rule_usage_event_rows(rows) -> list[dict]:
|
||||
|
||||
|
||||
def _moment_mapping_rows(rows) -> list[dict]:
|
||||
"""An install's action → moment corrections (milestone 458). Not
|
||||
"""An install's action → moment corrections. Not
|
||||
`to_dict()`, for _retrieval_tuning_event_rows' reason: the restore needs
|
||||
`user_id` to remap, and the MCP reader omits it."""
|
||||
return [
|
||||
@@ -581,7 +551,7 @@ def _moment_mapping_rows(rows) -> list[dict]:
|
||||
|
||||
|
||||
def _user_profile_rows(rows) -> list[dict]:
|
||||
"""The Profile page (#5879). Not `to_dict()`: that one fills blanks with
|
||||
"""The Profile page. Not `to_dict()`: that one fills blanks with
|
||||
the page's defaults and omits `user_id`, and a restore needs the stored
|
||||
value and the owner."""
|
||||
return [
|
||||
@@ -599,7 +569,7 @@ def _user_profile_rows(rows) -> list[dict]:
|
||||
|
||||
|
||||
def _retrieval_tuning_event_rows(rows) -> list[dict]:
|
||||
"""The record of why a retrieval dial is where it is (#4102).
|
||||
"""The record of why a retrieval dial is where it is.
|
||||
|
||||
Not `to_dict()`: that method serves the MCP reader, which already knows
|
||||
whose install it is asking about, so it omits `user_id`. A restore has to
|
||||
@@ -611,8 +581,8 @@ def _retrieval_tuning_event_rows(rows) -> list[dict]:
|
||||
"user_id": r.user_id, "surface": r.surface, "dial": r.dial,
|
||||
"old_value": r.old_value, "new_value": r.new_value,
|
||||
"actor": r.actor, "reason": r.reason,
|
||||
# Carried, and NOT defaulted to the current model on the way out
|
||||
# (#4104): a row that was unstamped when it was written is still
|
||||
# Carried, and NOT defaulted to the current model on the way
|
||||
# out: a row that was unstamped when it was written is still
|
||||
# unstamped after a round trip, and a backup that quietly filled
|
||||
# the gap would turn "we don't know" into a stated fact.
|
||||
"embedding_model": r.embedding_model,
|
||||
@@ -637,9 +607,9 @@ def _code_shape_use_rows(rows) -> list[dict]:
|
||||
|
||||
def _repo_binding_rows(rows) -> list[dict]:
|
||||
return [
|
||||
# `ref` is the branch this binding's ledger follows (#2873). Without
|
||||
# it a restored binding silently falls back to the default branch, and
|
||||
# the shape ledger starts accounting for a different tree (#3182).
|
||||
# `ref` is the branch this binding's ledger follows. Without it a
|
||||
# restored binding silently falls back to the default branch, and the
|
||||
# shape ledger starts accounting for a different tree.
|
||||
{
|
||||
"user_id": r.user_id, "project_id": r.project_id,
|
||||
"repo_key": r.repo_key, "ref": r.ref,
|
||||
@@ -649,9 +619,8 @@ def _repo_binding_rows(rows) -> list[dict]:
|
||||
|
||||
|
||||
# Row builders for the sections both exporters carry. Pure, like the join-table
|
||||
# helpers above; the full and per-user exports used to restate every one of
|
||||
# these comprehensions side by side, and a column added to one and not the
|
||||
# other is a backup that silently drops it (#2293's shape, one layer down).
|
||||
# helpers above, and shared so the full and per-user exports cannot drift: a
|
||||
# column added to one and not the other is a backup that silently drops it.
|
||||
|
||||
def _user_rows(rows) -> list[dict]:
|
||||
return [
|
||||
@@ -685,10 +654,10 @@ def _milestone_rows(rows) -> list[dict]:
|
||||
{
|
||||
"id": m.id, "user_id": m.user_id, "project_id": m.project_id,
|
||||
"title": m.title, "description": m.description, "status": m.status,
|
||||
# THE PLAN. A milestone IS the plan (0066) and `body` is its
|
||||
# design and intent; `description` is only the one-line summary.
|
||||
# Dropping this restored every plan as a title with no reasoning
|
||||
# behind it (#3182).
|
||||
# THE PLAN. A milestone IS the plan and `body` is its design and
|
||||
# intent; `description` is only the one-line summary. Dropping
|
||||
# this would restore every plan as a title with no reasoning
|
||||
# behind it.
|
||||
"body": m.body,
|
||||
"order_index": m.order_index,
|
||||
"created_at": m.created_at.isoformat(),
|
||||
@@ -709,11 +678,10 @@ def _note_rows(rows) -> list[dict]:
|
||||
"due_date": n.due_date.isoformat() if n.due_date else None,
|
||||
"created_at": n.created_at.isoformat(),
|
||||
"updated_at": n.updated_at.isoformat(),
|
||||
# WHAT KIND OF RECORD THIS IS — both typing axes (#3182). Missing
|
||||
# until now, which meant a restore reported success and handed back
|
||||
# a corpus with every snippet and process flattened into a plain
|
||||
# note and every issue and spike into `work`. Nothing recomputes
|
||||
# these; the vocabulary is simply gone.
|
||||
# WHAT KIND OF RECORD THIS IS — both typing axes. Without them a
|
||||
# restore reports success and hands back a corpus with every
|
||||
# snippet and process flattened into a plain note and every issue
|
||||
# and spike into `work`. Nothing recomputes these.
|
||||
"note_type": n.note_type,
|
||||
"task_kind": n.task_kind,
|
||||
# Provenance — which record caused this one. Re-mapped in the
|
||||
@@ -735,7 +703,7 @@ def _note_rows(rows) -> list[dict]:
|
||||
n.recurrence_next_spawn_at.isoformat()
|
||||
if n.recurrence_next_spawn_at else None
|
||||
),
|
||||
# The verification trio (milestone 317, migration 0092). Operator
|
||||
# The verification trio. Operator
|
||||
# judgment — "somebody checked this fact, and this is when" —
|
||||
# which nothing can recompute.
|
||||
"verify_with": n.verify_with,
|
||||
@@ -885,7 +853,7 @@ def _rule_relation_rows(rows) -> list[dict]:
|
||||
|
||||
def _lesson_rule_link_rows(rows) -> list[dict]:
|
||||
"""Which rule each lesson is an instance of, with the judgment's state,
|
||||
reason and evidence (milestone 440). Ids are SOURCE ids, remapped through
|
||||
reason and evidence. Ids are SOURCE ids, remapped through
|
||||
the note and rule maps at restore."""
|
||||
return [
|
||||
{
|
||||
@@ -899,7 +867,7 @@ def _lesson_rule_link_rows(rows) -> list[dict]:
|
||||
|
||||
|
||||
def _lesson_no_rule_rows(rows) -> list[dict]:
|
||||
"""The "no rule fits" answers, by SOURCE lesson id (#4631)."""
|
||||
"""The "no rule fits" answers, by SOURCE lesson id."""
|
||||
return [
|
||||
{
|
||||
"lesson_id": r.lesson_id, "why": r.why,
|
||||
@@ -1322,7 +1290,7 @@ async def export_user_backup(user_id: int) -> dict:
|
||||
select(RuleMomentJudgment).where(RuleMomentJudgment.rule_id.in_(_rule_ids))
|
||||
)).scalars().all() if _rule_ids else []
|
||||
# Scoped through the RULE, not the version's user_id. That column is
|
||||
# the ACTOR (milestone 323), so filtering on it would carry the
|
||||
# the ACTOR, so filtering on it would carry the
|
||||
# versions this user wrote on someone ELSE's rule and drop the ones
|
||||
# someone else wrote on theirs — the opposite of a per-user export.
|
||||
rule_versions = (await session.execute(
|
||||
@@ -1478,26 +1446,24 @@ async def export_user_backup(user_id: int) -> dict:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-table builders — the import side's twin of the row helpers above (#4197)
|
||||
# Per-table builders — the import side's twin of the row helpers above
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# The column guard (#3182) makes a dropped column unexpressible on the way OUT
|
||||
# because export goes through per-table pure helpers: hand one a stand-in and
|
||||
# read which KEYS came back. Nothing watched the way back IN, and that is the
|
||||
# worse half — an export gap leaves an obviously thin backup, an import gap
|
||||
# means holding a complete, correct file and restoring an incomplete database
|
||||
# from it, with a success message.
|
||||
# The column guard makes a dropped column unexpressible on the way OUT because
|
||||
# export goes through per-table pure helpers: hand one a stand-in and read
|
||||
# which KEYS came back. The way back IN is the worse half to leave unwatched —
|
||||
# an export gap leaves an obviously thin backup, an import gap means holding a
|
||||
# complete, correct file and restoring an incomplete database from it, with a
|
||||
# success message.
|
||||
#
|
||||
# The guard was one-sided because the code was. `_restore_v2` was one long
|
||||
# procedural function with the model construction inlined in each loop, so
|
||||
# there was no per-table unit to hand a stand-in to. These builders are that
|
||||
# unit. Each takes an exported row plus the id maps built so far and returns
|
||||
# either the model instance to add or None for a row that cannot be restored.
|
||||
# These builders are the per-table unit the import guard hands a stand-in to.
|
||||
# Each takes an exported row plus the id maps built so far and returns either
|
||||
# the model instance to add or None for a row that cannot be restored.
|
||||
#
|
||||
# WHAT DELIBERATELY DID NOT MOVE. The loops, the flushes and the id-map
|
||||
# bookkeeping stay in `_restore_v2`: that is the order-dependent part, where a
|
||||
# mistake is a restore that half-works, and it gains nothing from being split.
|
||||
# Only the construction moved, because the construction is the only thing the
|
||||
# WHAT DELIBERATELY STAYS IN `_restore_v2`: the loops, the flushes and the
|
||||
# id-map bookkeeping. That is the order-dependent part, where a mistake is a
|
||||
# restore that half-works, and it gains nothing from being split. Only the
|
||||
# construction lives here, because the construction is the only thing the
|
||||
# guard has to see.
|
||||
#
|
||||
# RETURNING None IS "SKIP", A None FIELD IS "DEGRADE", and the difference is
|
||||
@@ -1589,7 +1555,7 @@ def _build_milestone(row: dict, maps: _Maps) -> Milestone | None:
|
||||
def _build_note(row: dict, maps: _Maps) -> Note | None:
|
||||
"""`parent_id` and `arose_from_id` land NULL and are patched afterwards.
|
||||
|
||||
Both are ids in the SOURCE database (#3182), and the target note may not
|
||||
Both are ids in the SOURCE database, and the target note may not
|
||||
have been created yet when this row is built.
|
||||
"""
|
||||
uid = maps.users.get(row.get("user_id", 0))
|
||||
@@ -1733,9 +1699,9 @@ def _build_retrieval_tuning_event(row: dict, maps: _Maps) -> RetrievalTuningEven
|
||||
new_value=row.get("new_value"),
|
||||
actor=row.get("actor") or "model",
|
||||
reason=row.get("reason", ""),
|
||||
# .get with no default, deliberately (v17): an archive written before
|
||||
# the stamp existed has no key here, and None is the right answer for
|
||||
# it — the same "unstamped" a pre-#4104 row carries in place.
|
||||
# .get with no default, deliberately: an archive older than v17 has
|
||||
# no key here, and None is the right answer for it — the same
|
||||
# "unstamped" an unstamped row carries in place.
|
||||
embedding_model=row.get("embedding_model"),
|
||||
shape_version=row.get("shape_version"),
|
||||
created_at=_dt(row.get("created_at")),
|
||||
@@ -1784,16 +1750,15 @@ def _build_rule(row: dict, maps: _Maps) -> Rule | None:
|
||||
why=row.get("why") or None,
|
||||
how_to_apply=row.get("how_to_apply") or None,
|
||||
when_to_apply=row.get("when_to_apply") or None,
|
||||
# A file written before milestone 394 carries `tier` and `always_on`;
|
||||
# neither is read. Dropping a field the schema no longer has is the
|
||||
# tolerant direction — an archive records what WAS, and refusing it
|
||||
# because it remembers a deleted column would make every pre-394
|
||||
# backup unrestorable.
|
||||
# An older archive may carry `tier` and `always_on`; neither is read.
|
||||
# Dropping a field the schema does not have is the tolerant direction
|
||||
# — an archive records what WAS, and refusing it because it remembers
|
||||
# a deleted column would make every such backup unrestorable.
|
||||
#
|
||||
# Same shape, same reason: a file written before 0098 has no kind, and
|
||||
# every rule in it was a rule. Defaulting the other way would restore
|
||||
# an old backup with things that had always bound quietly no longer
|
||||
# binding.
|
||||
# Same shape, same reason: an archive with no `kind` predates
|
||||
# preferences, and every rule in it is a rule. Defaulting the other
|
||||
# way would restore an old backup with things that had always bound
|
||||
# quietly not binding.
|
||||
kind=row.get("kind") or "rule",
|
||||
verify_with=row.get("verify_with") or None,
|
||||
expires_when=row.get("expires_when") or None,
|
||||
@@ -1802,9 +1767,8 @@ def _build_rule(row: dict, maps: _Maps) -> Rule | None:
|
||||
# it would put every constraint at the top of the sweep with nothing
|
||||
# having actually changed.
|
||||
verified_at=_dt_or_none(row.get("verified_at")),
|
||||
# Remapped through the note map like every other note edge. Exported
|
||||
# since 0088 but dropped on the way back in until milestone 312 — a
|
||||
# restore silently lost every rule's provenance link. SET NULL
|
||||
# Remapped through the note map like every other note edge; without
|
||||
# it a restore silently loses every rule's provenance link. SET NULL
|
||||
# semantics apply here too: a source note that didn't restore leaves
|
||||
# the rule intact.
|
||||
arose_from_id=maps.notes.get(row.get("arose_from_id") or 0),
|
||||
@@ -2056,7 +2020,7 @@ def _build_system(row: dict, maps: _Maps) -> System | None:
|
||||
color=row.get("color"),
|
||||
status=row.get("status", "active"),
|
||||
order_index=row.get("order_index", 0),
|
||||
# Absent in a backup taken before milestone 444: no paths, not an error.
|
||||
# Absent in an archive older than v20: no paths, not an error.
|
||||
path_patterns=list(row.get("path_patterns") or []),
|
||||
# An unknown slug restores UNMAPPED rather than failing: the System and
|
||||
# its records are the payload, the mapping is an aid.
|
||||
@@ -2142,7 +2106,7 @@ def _build_rule_usage_event(row: dict, maps: _Maps) -> RuleUsageEvent | None:
|
||||
"""Resolved through the RULE map, not the note map. A rule id run through
|
||||
the note map would either drop (best case) or land on whatever note took
|
||||
that number, producing telemetry that is wrong rather than missing and
|
||||
that nothing downstream could detect (milestone 333)."""
|
||||
that nothing downstream could detect."""
|
||||
rid = maps.rules.get(row.get("rule_id", 0))
|
||||
if rid is None:
|
||||
return None
|
||||
@@ -2217,10 +2181,10 @@ def _build_code_shape(row: dict, maps: _Maps) -> CodeShape | None:
|
||||
status=status,
|
||||
snippet_id=snippet_id,
|
||||
reason=row.get("reason"),
|
||||
# Exported since #2874 and dropped on the way back in until #4197 —
|
||||
# every judged row restored with its verdict and without the code for
|
||||
# WHY, which is the column the accounting reads to tell a scoped-css
|
||||
# exemption from a convention-plumbing one. Kept on a downgrade for
|
||||
# Without it every judged row restores with its verdict and without
|
||||
# the code for WHY, which is the column the accounting reads to tell a
|
||||
# scoped-css exemption from a convention-plumbing one. Kept on a
|
||||
# downgrade for
|
||||
# the same reason `reason` is: the free text and its code are one
|
||||
# statement, and the existing downgrade clears the judgment's author
|
||||
# and date, not its argument.
|
||||
@@ -2230,20 +2194,20 @@ def _build_code_shape(row: dict, maps: _Maps) -> CodeShape | None:
|
||||
first_seen_commit=row.get("first_seen_commit", ""),
|
||||
last_seen_commit=row.get("last_seen_commit", ""),
|
||||
vanished_at=_dt(row["vanished_at"]) if row.get("vanished_at") else None,
|
||||
# Fingerprints restore; proposals (#2792) deliberately do not — they
|
||||
# Fingerprints restore; proposals deliberately do not — they
|
||||
# are mechanical, and the next refresh recomputes them against the
|
||||
# restored snippet ids.
|
||||
signature=row.get("signature", ""),
|
||||
body_sha=row.get("body_sha", ""),
|
||||
classified_sha=row.get("classified_sha", ""),
|
||||
# Both exported and both dropped until #4197. `recheck_at` is the
|
||||
# standing "this judgment asks to be confirmed again" flag, so losing
|
||||
# `recheck_at` is the standing "this judgment asks to be confirmed
|
||||
# again" flag, so losing
|
||||
# it restores a tree that looks settled and is not.
|
||||
recheck_at=_dt(row["recheck_at"]) if row.get("recheck_at") else None,
|
||||
# RE-MAPPED, not carried: it is a FK to notes.id like `snippet_id`,
|
||||
# and a raw source id would point at whatever snippet took that
|
||||
# number in the destination — wrong rather than missing, which is the
|
||||
# milestone 333 trap one table over.
|
||||
# number in the destination — wrong rather than missing, the same
|
||||
# trap `_build_rule_usage_event` avoids.
|
||||
diverges_from=maps.notes.get(row.get("diverges_from") or 0),
|
||||
created_at=_dt(row.get("created_at")),
|
||||
updated_at=_dt(row.get("updated_at")),
|
||||
@@ -2279,8 +2243,8 @@ def _build_code_shape_event(row: dict, maps: _Maps) -> CodeShapeEvent | None:
|
||||
|
||||
|
||||
def _build_code_shape_use(row: dict, maps: _Maps) -> CodeShapeUse | None:
|
||||
"""Consumption edges (#2870) ride their shape AND their snippet — both
|
||||
ends must have survived, or the edge is no longer a fact."""
|
||||
"""Consumption edges ride their shape AND their snippet — both ends
|
||||
must have survived, or the edge is no longer a fact."""
|
||||
shape_id = maps.shapes.get(row.get("shape_id") or 0)
|
||||
snippet_id = maps.notes.get(row.get("snippet_id") or 0)
|
||||
if shape_id is None or snippet_id is None:
|
||||
@@ -2377,7 +2341,7 @@ async def _restore_v1(data: dict) -> dict:
|
||||
# Patch the two note->note edges now that every note has a new id.
|
||||
# Both are ids in the SOURCE database, so writing either straight into
|
||||
# the constructor would point at whatever record happens to hold that
|
||||
# number here — a restore that succeeds and silently re-parents (#3182).
|
||||
# number here — a restore that succeeds and silently re-parents.
|
||||
# An edge whose target did not survive the import is left NULL rather
|
||||
# than guessed at.
|
||||
for n_data in data.get("notes", []):
|
||||
@@ -2417,7 +2381,7 @@ async def _restore_v2(data: dict) -> dict:
|
||||
|
||||
Every model is constructed by a `_build_*` helper above, never inline.
|
||||
That is what gives the import side a per-table unit the column guard can
|
||||
hand a stand-in to (#4197); what stays here is the ordering, the flushes
|
||||
hand a stand-in to; what stays here is the ordering, the flushes
|
||||
and the id-map bookkeeping, which is the part a split would only make
|
||||
harder to read.
|
||||
"""
|
||||
@@ -2488,8 +2452,8 @@ async def _restore_v2(data: dict) -> dict:
|
||||
stats["notes"] += 1
|
||||
|
||||
# 4b. Patch the note->note edges. Deferred for the same reason
|
||||
# parent_id always has been: these are ids in the SOURCE database
|
||||
# (#3182). An edge whose target did not survive stays NULL.
|
||||
# parent_id is: these are ids in the SOURCE database. An edge whose
|
||||
# target did not survive stays NULL.
|
||||
for new_note_id, old_parent_id in notes_with_parents:
|
||||
new_parent_id = maps.notes.get(old_parent_id)
|
||||
if new_parent_id:
|
||||
@@ -2537,8 +2501,8 @@ async def _restore_v2(data: dict) -> dict:
|
||||
|
||||
# 8b. Retrieval tuning history (v16) — restored beside the settings it
|
||||
# explains, and for the same reason: the numbers above are the state,
|
||||
# these rows are the argument for it. From milestone 416 those dials
|
||||
# move on the operator's behalf, so an install restored with the values
|
||||
# these rows are the argument for it. Those dials move on the
|
||||
# operator's behalf, so an install restored with the values
|
||||
# and without the reasons is one tuned by nobody it can name.
|
||||
for t_data in data.get("retrieval_tuning_events", []):
|
||||
event = _build_retrieval_tuning_event(t_data, maps)
|
||||
@@ -2596,15 +2560,14 @@ async def _restore_v2(data: dict) -> dict:
|
||||
|
||||
# 12-14. Rulebook subscriptions, rule and topic suppressions (v3-v14)
|
||||
# `rulebook_subscriptions`, `rule_suppressions` and `topic_suppressions`
|
||||
# are READ BY NOBODY since milestone 414 dropped their tables. An
|
||||
# archive carrying them still imports, for the reason 14b gives.
|
||||
# are READ BY NOBODY: their tables do not exist. An archive carrying
|
||||
# them still imports, for the reason 14b gives.
|
||||
|
||||
# 14b. Always-on rulebook exclusions (v10, milestone 297)
|
||||
# `rulebook_exclusions` was a v10 section and is READ BY NOBODY since
|
||||
# milestone 394 removed the table. An archive carrying it still
|
||||
# imports — the key is simply not looked at — because refusing a
|
||||
# backup for remembering something we deleted would make every v10-v13
|
||||
# archive unrestorable.
|
||||
# 14b. Always-on rulebook exclusions (v10)
|
||||
# `rulebook_exclusions` is a v10 section READ BY NOBODY: its table
|
||||
# does not exist. An archive carrying it still imports — the key is
|
||||
# simply not looked at — because refusing a backup for remembering
|
||||
# something deleted would make every such archive unrestorable.
|
||||
|
||||
# --- v5 sections. Every one is data.get()-guarded, so a v2/v3/v4
|
||||
# payload restores without them rather than failing on an absent key.
|
||||
@@ -2669,7 +2632,7 @@ async def _restore_v2(data: dict) -> dict:
|
||||
session.add(relation)
|
||||
stats["rule_relations"] += 1
|
||||
|
||||
# Lesson → rule links (milestone 440): after both notes and rules are
|
||||
# Lesson → rule links: after both notes and rules are
|
||||
# mapped, which is why they sit beside the rule edges. Archives before
|
||||
# v18 carry no section and restore with none.
|
||||
for lr in data.get("lesson_rule_links", []):
|
||||
@@ -2678,7 +2641,7 @@ async def _restore_v2(data: dict) -> dict:
|
||||
continue
|
||||
session.add(link)
|
||||
stats["lesson_rule_links"] += 1
|
||||
# "No rule fits" answers (#4631); archives before v19 carry none.
|
||||
# "No rule fits" answers; archives before v19 carry none.
|
||||
for nr in data.get("lesson_no_rule", []):
|
||||
answer = _build_lesson_no_rule(nr, maps)
|
||||
if answer is None:
|
||||
@@ -2746,9 +2709,9 @@ async def _restore_v2(data: dict) -> dict:
|
||||
maps.decisions[int(fd["id"])] = decision.id
|
||||
stats["family_decisions"] += 1
|
||||
|
||||
# A rule's edit history (milestone 323). Must come after the rules
|
||||
# themselves — the rule map is only populated above — and both ids are
|
||||
# ids in the SOURCE database, which is #3182's arose_from_id trap.
|
||||
# A rule's edit history. Must come after the rules themselves — the
|
||||
# rule map is only populated above — and both ids are ids in the
|
||||
# SOURCE database, the same trap as a note's arose_from_id.
|
||||
for rv in data.get("rule_versions", []):
|
||||
version = _build_rule_version(rv, maps)
|
||||
if version is None:
|
||||
@@ -2834,7 +2797,7 @@ async def _restore_v2(data: dict) -> dict:
|
||||
session.add(binding)
|
||||
stats["repo_bindings"] += 1
|
||||
|
||||
# 21. Code shapes (v7, #2787)
|
||||
# 21. Code shapes (v7)
|
||||
for cs_data in data.get("code_shapes", []):
|
||||
shape = _build_code_shape(cs_data, maps)
|
||||
if shape is None:
|
||||
@@ -2853,7 +2816,7 @@ async def _restore_v2(data: dict) -> dict:
|
||||
session.add(event)
|
||||
stats["code_shape_events"] += 1
|
||||
|
||||
# v9: consumption edges (#2870).
|
||||
# v9: consumption edges.
|
||||
for use in data.get("code_shape_uses", []):
|
||||
edge = _build_code_shape_use(use, maps)
|
||||
if edge is None:
|
||||
@@ -2882,13 +2845,13 @@ async def _restore_v2(data: dict) -> dict:
|
||||
inception = p_data.get("inception")
|
||||
if isinstance(inception, dict):
|
||||
choices = dict(inception.get("choices") or {})
|
||||
# DROPPED, not remapped (milestone 394). A pre-394 archive
|
||||
# carries the retired exclusion choice; restoring it would put
|
||||
# DROPPED, not remapped. An older archive may carry the
|
||||
# retired exclusion choice; restoring it would put
|
||||
# a key back that `validate_inception` now rejects as unknown,
|
||||
# so the next edit to that project would fail on data this
|
||||
# importer wrote.
|
||||
choices.pop("exclude_always_on_rulebooks", None)
|
||||
# Same for subscribe_rulebooks since milestone 414.
|
||||
# Same for the retired subscribe_rulebooks choice.
|
||||
choices.pop("subscribe_rulebooks", None)
|
||||
ds = choices.get("design_system_id")
|
||||
choices["design_system_id"] = maps.design_systems.get(ds) if ds else None
|
||||
|
||||
@@ -224,8 +224,8 @@ async def _apply_supersession_penalty(
|
||||
stale = await superseded_ids([int(note.id) for _score, note in scored])
|
||||
except Exception:
|
||||
# Fail OPEN, and the direction matters: ranking without the penalty is
|
||||
# the behaviour that shipped for months. Returning nothing, or raising,
|
||||
# would turn a supersession-lookup hiccup into a broken search.
|
||||
# a slightly worse search. Returning nothing, or raising, would turn a
|
||||
# supersession-lookup hiccup into a broken one.
|
||||
logger.warning("Supersession lookup failed — ranking unpenalised", exc_info=True)
|
||||
return scored[:limit]
|
||||
|
||||
|
||||
@@ -985,13 +985,12 @@ PULL_WINDOW = timedelta(hours=6)
|
||||
# THE PROBLEM THIS EXISTS FOR. `kind` has exactly two values, so a frozen
|
||||
# dataclass, a module constant, a sync predicate, a class and an async service
|
||||
# function are all siblings of one another. Nothing downstream could possibly
|
||||
# discriminate on a distinction the column does not carry — which is how a
|
||||
# directory base rate ended up standing alone as the entire divergence
|
||||
# argument. It was not chosen over a better signal; it was the only signal
|
||||
# there was. Measured consequence: writing a registry module of pure helpers
|
||||
# produced five prompts to build them from the `async_session` service canon,
|
||||
# and on another project one snippet had been recorded as the canon for a
|
||||
# `class`, a plain `def`, an `async def` and a dozen test functions at once.
|
||||
# discriminate on a distinction the column does not carry, which leaves a
|
||||
# directory base rate standing alone as the entire divergence argument. The
|
||||
# consequence: a registry module of pure helpers gets prompted to build from
|
||||
# an `async_session` service canon, and one snippet can be recorded as the
|
||||
# canon for a `class`, a plain `def`, an `async def` and a dozen test
|
||||
# functions at once.
|
||||
#
|
||||
# DERIVED FROM THE SIGNATURE ON READ, not stored. `kind` is part of the row
|
||||
# identity — ("project_id", "repo_key", "path", "symbol", "kind") — so
|
||||
|
||||
Reference in New Issue
Block a user