Skip to content

[needs human review: schema] Stale "Working on": dim after 90 days, owner confirm, digest nudge - #139

Closed
0xSY3 wants to merge 1 commit into
devfrom
feat/cards-stale-working-on
Closed

0xSY3 wants to merge 1 commit into
devfrom
feat/cards-stale-working-on

Conversation

@0xSY3

@0xSY3 0xSY3 commented Oct 28, 2026

Copy link
Copy Markdown
Member

Why

Signals rot. A card's Working on is written once at onboarding and rarely touched, so a six-month-old list keeps telling visitors and AI agents what someone was doing last spring. Beacons already expire after 7 days; Working on had no freshness signal at all, because nothing recorded when it last changed.

What

  • Timestamp (cards migration 0033, schema change, needs a human, not applied anywhere). Adds a nullable cards.agent_profile_cards.working_on_updated_at and a BEFORE INSERT OR UPDATE OF card, working_on_updated_at trigger that stamps it whenever card->'working_on' changes and mirrors it into card->'working_on_updated_at'.
    • Why a trigger and not an API-side stamp: the list is also written in SQL. cards.s10_save_section (editor section saves) and cards.companion_save_fields (the browser extension) only accept fixed key lists, so cards-api cannot add a stamp key there without a racy second write. The trigger covers every writer: publish/republish upsert, editor full-card PATCH, beacons, both RPCs, memory refresh, and anything run by hand.
    • The column is the authority. The trigger rewrites the JSON mirror from the column on every write, so a stale copy of the card JSON (read-modify-write in cards-api) cannot move the stamp. Writes that leave working_on unchanged keep the stamp. An explicit column write (the confirm) is kept, and is clamped so it can never be in the future.
    • No backfill. Existing rows stay NULL, which means unknown, which is never stale. Their card JSON is unchanged.
    • Tolerates being absent. Until it is applied, no card has a stamp, so nothing is dimmed and no digest line goes out. The confirm endpoint answers 503.
  • cards-api
    • services/stale_signals.py: STALE_DAYS = 90. working_on_is_stale() is true only when the list is non-empty, the stamp is known, and the stamp is at least 90 days old.
    • services/signals.py: live is_working_on memory facts are merged into the public list, so the public stamp moves up to the newest of them (capped at now).
    • POST /cards/by-handle/{handle}/working-on/confirm: owner-only, using the existing _principal/_owner_email JWT pattern. It returns 401 unsigned, 403 for a non-owner, 404 for an unknown card or flag off, and 503 before 0033. It writes only the column and does not change the list.
    • The working_on_updated_at field is added to AgentProfileCard and CardView. It is omitted from the dump when unknown, so legacy cards are stored byte-identical.
    • Morning digest (services/digest.py): when the owner's public Working on is stale, it adds one "Working on" row, "Still working on this? … Confirm / Update it", in both plain text and HTML, built with the existing _label/_box/_row helpers. Confirm needs the owner's session, so it links to their card (/p/<handle>#working-on), where the button is. Update links to /p/<handle>/edit#signals. Nothing else in the digest changes: subject, preheader and intro are untouched.
  • cards-web
    • Classic and edition (Mumbai) cards: stale Working on chips render at 55% opacity with a "Not updated in 3+ months" hint, for everyone.
    • The owner also sees Still working on this? Confirm · Update. Confirm calls the new endpoint with the session token, then router.refresh(). It is an event handler, not an effect.
    • data.json: adds zynd:working_on_updated_at when known, so AI clients can judge freshness. This is additive only.

Flags (both default off)

Flag Where Off means
FEATURE_STALE_SIGNALS cards-api confirm endpoint 404, no digest line
NEXT_PUBLIC_FEATURE_STALE_SIGNALS cards-web (build time) no dimming, hint or prompt; no zynd:working_on_updated_at in data.json

Not flagged (my choice): the DB stamping, and the working_on_updated_at field in cards-api's card JSON responses (/cards/by-handle/*, /view, /mine). Both are harmless and additive, and the field only has a value once 0033 is applied. Gating the stamp would mean losing history until the flag flips.

Migration and rollback

packages/db/cards/migrations/0033_stale_working_on.sql (-- owner: cards, generated by cards:generate, then the trigger appended, with journal and snapshot). It is expand-only, and cards-api tolerates it being applied before or after the deploy.

-- Rollback (cards-api keeps working; the stale hints simply stop appearing):
DROP TRIGGER IF EXISTS agent_profile_cards_working_on_stamp ON cards.agent_profile_cards;
DROP FUNCTION IF EXISTS cards.stamp_working_on_updated_at();
UPDATE cards.agent_profile_cards SET card = card - 'working_on_updated_at' WHERE card ? 'working_on_updated_at';
ALTER TABLE cards.agent_profile_cards DROP COLUMN IF EXISTS working_on_updated_at;

There is no staging DB, so applying it is live in prod. Please review and apply per packages/db/README.md step 6.

Test evidence

Suite Before After
cards-api pytest -q 898 passed, 2 failed (known test_x_bot.py), 23 skipped 926 passed, same 2 failed, 25 skipped (the 2 new Postgres tests skip without STALE_TEST_DATABASE_URL)
cards-api tests/test_stale_working_on_postgres.py on local disposable Postgres 15 — 2 passed
cards-web npm run lint 0 errors, 24 warnings 0 errors, 24 warnings (all pre-existing)
cards-web npx tsc --noEmit clean clean
cards-web npm run build (flag on) — succeeds
cards-web src/lib/stale-signals.test.ts (tsx --test) — 4 passed
packages/db db:lint, db:check, typecheck — ok
packages/db npm test with CARDS_TEST_DATABASE_URL (local) — 27 passed, 1 skipped (memory DB)
  • packages/db/test/cards-stale-working-on-sql.test.ts, run on the full history on a local Postgres, covers these cases:
    • insert stamps; an empty list gets no stamp
    • s10_save_section: another section keeps the stamp, the Working on section restamps
    • companion_save_fields restamps
    • a full-card write without the key, or with a stale mirror, keeps the stamp
    • upsert keeps the stamp when the list is unchanged and restamps when it changes
    • a status-only update skips the trigger
    • a future confirm is clamped
    • a legacy NULL row stays unknown
  • The cards-network and cards-reconcile tests were updated for the new migration count and nullable column, as with 0029. Reconcile also proves pre-existing rows' card JSON is unchanged.
  • I also checked the pages by hand with next dev against a mock API:
    • classic and edition chips are dimmed with the hint, including at phone width
    • the owner prompt rendered; clicking Confirm sent the bearer POST, and the refresh un-dimmed the card
    • fresh and legacy cards are not dimmed
    • data.json carries the stamp
  • I rendered the digest HTML and checked it in a browser.

Not verified

  • Against real Supabase/PostgREST. Only plain local Postgres and mocks were used, so the PostgREST error code for a missing column is assumed to be PGRST204 or 42703; both are handled as 503.
  • The owner flow was checked with a mocked session, not a real sign-in.

🤖 Generated with Claude Code

A card's Working on is written once at onboarding and rarely touched, so a
six-month-old list tells visitors and AI agents what someone did last spring.
Nothing recorded when it last changed, so nothing could say it was old.

- cards migration 0033 (expand-only, NOT applied anywhere): nullable
  agent_profile_cards.working_on_updated_at plus a BEFORE INSERT/UPDATE OF card
  trigger that stamps it whenever card->'working_on' changes and mirrors it into
  the card JSON. A trigger because the list is also written in SQL
  (s10_save_section, companion_save_fields), where cards-api cannot add a key;
  the column is the authority, so a stale copy of the card JSON cannot move it.
  No backfill: existing rows stay NULL = unknown = never stale.
- cards-api: STALE_DAYS=90 rule (services/stale_signals.py); live is_working_on
  memory facts count as fresh; owner-only POST
  /cards/by-handle/{handle}/working-on/confirm (401 unsigned, 403 non-owner,
  503 until 0033 is applied); one "Working on" row in the morning digest.
- cards-web: dimmed chips + "Not updated in 3+ months" on classic and edition
  cards, "Still working on this? Confirm · Update" for the owner, and
  zynd:working_on_updated_at in data.json.
- Behind FEATURE_STALE_SIGNALS / NEXT_PUBLIC_FEATURE_STALE_SIGNALS, both off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@0xSY3

0xSY3 commented Oct 28, 2026

Copy link
Copy Markdown
Member Author

Superseded by #140, which combines all five features with fixes from a four-way review. The branch is kept.

@0xSY3 0xSY3 closed this Oct 28, 2026
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