Skip to content

fix: route the usage table through getTableName and make setup.sql re-applicable (GIT-84) - #33

Merged
2 commits merged into
mainfrom
bugfix/GIT-84-table-prefix-sweep
Sep 21, 2026
Merged

2 commits merged into
mainfrom
bugfix/GIT-84-table-prefix-sweep

Conversation

@nTEG-dev

@nTEG-dev nTEG-dev commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Linear: https://linear.app/nteg-labs/issue/GIT-84

⚠️ nTEG's own upgrade requires a privately held migration (rename scar_usage → orchestra_scar_usage, kept in the private orchestra repo). Customer stores need nothing.

What

1. The usage table now goes through getTableName("scar_usage"). Every customer store has gitmem_scar_usage, so the literal "scar_usage" 404'd on every usage write and read.

Site Before After
tools/record-scar-usage.ts:64 directUpsert("scar_usage") getTableName("scar_usage")
tools/record-scar-usage-batch.ts:164 directUpsert("scar_usage") getTableName("scar_usage")
services/analytics.ts:204 directQueryAll("scar_usage") getTableName("scar_usage")
services/behavioral-decay.ts:101 REST /scar_usage REST /${getTableName("scar_usage")}
tools/record-scar-usage.ts:73, tools/session-close.ts:266 storage.upsert("scar_usage") unchanged: the storage layer already applies getTableName(collection), so changing these would double-prefix

The effect-tracker labels (track("scar_usage", …)) and the local collection name in commands/migrate-local.ts are not table names and are left alone.

2. Guard test (tests/unit/table-name-literals.test.ts). It fails on a bare table-name literal passed to directUpsert, directQuery, directQueryAll or directPatch, in a table: option, or in a REST URL. It also checks the reverse for storage: storage must not be handed a prefixed name or getTableName(). Against origin/main it flags exactly the four sites above.

3. setup.sql:

  • refresh_scar_behavioral_scores() now only rewrites rows whose value changes: AND l.decay_multiplier IS DISTINCT FROM <same GREATEST(...)>. This has to ship with this PR: once usage rows start landing, the old UPDATE bumps updated_at on every session_start and invalidates PR fix: cache the embedding index on disk so a cold process start costs bytes, not megabytes (GIT-98) #31's disk cache.
  • Adds the missing DROP POLICY IF EXISTS for gitmem_licenses and gitmem_license_activations, so setup.sql can be re-applied. Before this, a second run failed at statement 72.
  • scar_enforcement_variants.active isn't touched; that's GIT-106.
  • New tests/unit/schema-setup-reapply.test.ts checks: every policy and trigger is dropped before it's created, tables and indexes use IF NOT EXISTS, and the IS DISTINCT FROM guard is present. Two of its four tests fail on the old setup.sql.

Sweep: other string-literal table names (report only, need a ruling)

setup.sql creates these under exactly these names, or not at all, so changing only the code would break them. They're allowlisted in the guard test with the reasons below.

Literal Sites In setup.sql? Note
gitmem_query_metrics services/metrics.ts:169, :369, :383, :407 yes, with a hard-coded gitmem_ prefix ignores GITMEM_TABLE_PREFIX
knowledge_triples services/triple-writer.ts:290, services/supabase-client.ts:421, tools/graph-traverse.ts:165, :174, :261 yes, unprefixed
scar_enforcement_variants services/variant-assignment.ts:69, services/variant-generation.ts:322 yes, unprefixed .active column drift is GIT-106
variant_assignments services/variant-assignment.ts:100, :139 no 404 on every customer store
variant_performance_metrics tools/recall.ts:606 no 404 on every customer store
/rest/v1/gitmem_learnings, /rest/v1/gitmem_${table} commands/check.ts:145, :354, :452 yes the check CLI hard-codes gitmem_; wrong under a custom prefix
community_feedback services/feedback-remote.ts:9 n/a gitmem's own feedback endpoint, not the user schema

Venue check: blank Supabase (setup.sql only, no edge functions)

  • setup.sql re-apply: applied the patched setup.sql a second time over the existing schema. It succeeded, where it previously failed at statement 72.
  • Driver flow (recall → confirm_scars → session_close): gitmem_scar_usage = 1 row (was 0 on main). There were zero scar_usage 404s, and health shows scar_usage 1/1 succeeded.
  • GIT-98 interaction (PR fix: cache the embedding index on disk so a cold process start costs bytes, not megabytes (GIT-98) #31 + GIT-104 + this PR stacked, 250 learnings, 5 scars with ≥3 usage rows each):
Start Bytes received Cache
1 cold 921,750 miss (no file)
2 100 hit
3, runs session_start #1 1,478 hit
4 921,830 miss, legitimate: the 5 seeded scars went from 1.0 to 0.7333
5, runs session_start #2 (no new usage) 2,652 hit
6 100 hit: IS DISTINCT FROM rewrote 0 rows

Known follow-up (surfaced by this fix, not fixed here)

  • Analytics and blindspot reads now fail with a 400 where they used to 404. analytics.ts:204 selects scar_title and scar_severity, which gitmem_scar_usage doesn't have, giving column gitmem_scar_usage.scar_title does not exist. Both reads are non-fatal, but they return nothing. It needs a ruling: add the columns to setup.sql, or join to learnings.
  • The venue driver (test/GIT-83-blank-supabase-driver) treats this as an unexpected failure and exits 1 on this branch. That's intended: remove the GIT-84 entry from EXPECTED_FAILURES and ticket the analytics 400 when this merges.

Tests

  • npx tsc --noEmit -p .: no errors
  • npm run test:unit (with HOME set to a temp dir): 76 files / 1221 tests passed. main is 74 / 1213, so this adds +8 tests: 4 table-name guard, 4 setup.sql re-apply safety.

No version bump and no publish.

🤖 Generated with Claude Code

…-applicable (GIT-84)

Scar usage was written to and read from the literal table "scar_usage". That
table does not exist on a customer store (setup.sql creates gitmem_scar_usage),
so every usage write 404'd and the loss went unnoticed. On a blank Supabase,
gitmem_scar_usage stayed at 0 rows after recall, confirm_scars and
session_close.

- record-scar-usage, record-scar-usage-batch, analytics and behavioral-decay now
  use getTableName("scar_usage"). The two storage.upsert("scar_usage") sites are
  unchanged: the storage layer already applies the prefix.
- Guard test: fails on a bare table-name literal passed to directUpsert,
  directQuery, directQueryAll or directPatch, in a `table:` option, or in a
  REST URL. It also fails if storage is handed an already-prefixed name.
  Literals that setup.sql creates under exactly that name are allowlisted
  pending a ruling.
- setup.sql:
  - refresh_scar_behavioral_scores() only rewrites rows whose decay_multiplier
    actually changes. Otherwise, once usage rows land, it bumps updated_at on
    every session_start and invalidates the GIT-98 disk cache.
  - Adds the missing DROP POLICY IF EXISTS for gitmem_licenses and
    gitmem_license_activations, so a second run no longer fails at statement 72.
- schema/migrations/GIT-84-ntegs-scar-usage-rename.sql: NOT EXECUTED. Needs
  human review. nTEG's install (prefix orchestra_) keeps usage in plain
  scar_usage; this renames it to orchestra_scar_usage and leaves a
  security_invoker compatibility view.

Tests: +8 (4 table-name guard, 4 setup.sql re-apply safety).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…age (GIT-84)

schema/ ships in the npm package, so the nTEG-specific rename migration would
have gone to every customer. It now lives in the private orchestra repo
(ops/migrations/). nTEG's own upgrade still needs that privately held
migration before it runs this release.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nTEG-dev nTEG-dev closed this pull request by merging all changes into main in b1b3cb9 Sep 21, 2026
@nTEG-dev
nTEG-dev deleted the bugfix/GIT-84-table-prefix-sweep branch September 23, 2026 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant