Skip to content

Comment slimming: 66% of comment mass is issue archaeology — delete it, migrate teaching prose to description/docs, and file upstream cards for every hand-copied platform constraint #1184

Closed

Description

@os-steve

Problem

Measured on main @ 2342811: 11,565 comment lines against 22,248 code lines (34%). Of 1,693 comment blocks, 498 (29%) cite an issue number — and those blocks are the long ones: 7,660 lines, i.e. 66% of all comment mass and ~21% of the whole tree (~90k tokens — larger than the entire business model's code). Hotspots: objects/ 48% comment, actions/ 47%, flows/ 45%, profiles/ 70%, sharing/ 65%.

Most of it is changelog narrative inlined into source ("until #595 only one rule existed…", "#557 dropped it on that premise and the premise was wrong"). In a metadata-driven app the constraint should live in the schema, not in prose; and in the reference app, comments are every fork author's input — a third of that input currently describes a past that no longer exists. That is worse than noise for an AI author: it actively feeds stale premises.

What to do, per comment block (judgment work, not a regex sweep)

  1. Archaeology ("before #N it was…", PR/issue play-by-play) → delete. History lives in git blame and the issue tracker.
  2. Load-bearing constraints — platform facts an author must know or they will write wrong code (QuickJS body-only hook bodies where module constants arrive undefined; flow templates interpolating lookup traversals to the literal "undefined"; _case-sla.ts's CALENDAR-HOURS warning; date-macro freeze-at-build-time) → keep, tightened to the constraint itself (drop the narrative of how it was discovered). Additionally: each of these is a precise coordinate of a platform expressiveness gap, hand-copied across many files. File one card per constraint family in objectstack proposing the platform make the mistake impossible (lint/compile-time refusal) — that is the reference app feeding the platform, and long-term it retires the comment entirely.
  3. Teaching prose (why this sharingModel, why this OWD) → migrate to the metadata's own description keys (ship in the artifact, visible in Studio, translatable) or content/docs; delete the comment.

Start with objects/ (4,910 comment lines, 42% of the total), then flows/, actions/.

Ordering & measurement

Activity

  1. added
    metadataDeclarative metadata — schema, security posture, UI surfaces
    pm:queueReady for the PM dispatch loop
    on Aug 17, 2026
  2. hotlong commented on Sep 2, 2026

    @hotlong
    Contributor

    Half-state repair — pm:dispatched → pm:queue, assignee cleared. R18, repo:hotcrm seat.

    This card has carried pm:dispatched + an assignee since 2026-08-31 with no agent behind it. The dispatching session ended on quota that morning and its brief chose to leave the label standing so the remaining phases would not be forgotten.

    ⛔ pm:dispatched means an agent is working on this right now. Two phases of real work were parked outside the queue for two days because the label said they were already being done. The record of what remains belongs in this comment — permanently readable — ⛔ not in a state flag that lies to every inventory read.

    What landed, and what remains

    Landed: phase 1 — PR #1418, "docs(objects): comment slimming phase 1 — delete issue archaeology, keep platform constraints", merged_at 2026-08-31T05:33:06Z, scoped to src/objects/.

    Remaining, and this card is now dispatchable for them:

    phase surface
    2 src/flows/
    3 src/actions/

    ⛔ The one instruction that must not be lost with the label

    Both remaining phases are re-targeted at the issue-citing metric, and:

    ⛔ Phase 2 must re-measure its own archaeology-to-constraint ratio. It does not inherit src/objects/'s.

    That is the whole reason these are separate phases rather than one sweep. src/objects/ measured 66% archaeology; that number is a property of those files. A dev who carries it into src/flows/ will delete against a budget nobody measured — and comment mass in flow files is disproportionately platform constraint, which this card exists to keep.

    ⚠️ Related and unresolved: #1423 asked whether a comment-volume gate should exist at all, because the #1183 ratchet is by design blind to this work — so phases 2 and 3 have no instrument. That card is closed; ⛔ do not let its closure be read as "a gate exists". The measurement is the dev's to make and to report.


    Generated by Claude Code

  3. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 2, 2026
  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 10, 2026
  5. self-assigned this
    on Sep 10, 2026
  6. os-steve commented on Sep 10, 2026

    @os-steve
    CollaboratorAuthor

    Claim: session_01DuzfS5chho38Yx1jxx9DEj → branch claude/issue-1184-comment-slimming-flows — phase 2 of 3, src/flows/ only

    Dispatched by the hotcrm PM seat (#1353). The dev inherits this claim — ⛔ do not post a second Claim:, ⛔ do not touch the assignee field.

    Base off main at 62e23f5 or later (⚠️ two PRs land ahead of you today: #1842 touches scripts/lib/, #1843 rewrites defaultExpanded → collapse across 13 src/objects/*.object.ts files. Neither is in your surface; fetch fresh anyway).
    Model: Opus. Deliverable: one PR, ready for review, pnpm verify green. ⛔ Do not enable auto-merge, ⛔ do not merge.

    What this dispatch inherits, verbatim, from the phase-1 record

    This is not a fresh reading of the card. Phase 1 (src/objects/) landed as merged PR #1418, and its acceptance (comment 5474170582) plus the half-state repair (comment 5517638422) already settled four things. ⛔ Do not re-derive them and ⛔ do not re-litigate them:

    1. The card's headline "66% of comment mass is archaeology" was never measured — it was inferred from "66% sits in blocks citing an issue number". In src/objects/ the large majority of issue-citing blocks turned out to be load-bearing platform constraints whose issue number is provenance, not content.
    2. ⇒ The metric is the ISSUE-CITING comment mass (ruling: option A). Phase 1 halved it, 3,957 → 1,991 lines. Total comment mass is an observation, ⛔ not a goal — it moved 9.7% and that was the correct outcome, not a shortfall.
    3. ⛔ Option B is rejected outright: do not reach a number by deleting design rationale. That is the regex sweep this card forbids, done slowly and by hand.
    4. ⚠️ There is no instrument. check-source-token-ratchet.mjs measures the comment-stripped surface and Add a source-token ratchet gate: comment-stripped tokens over the authored surface (src minus translations/ and data/), only-down #1183's own header says it will not move on this work; [Decision] 要不要加一道注释量门禁?#1183 的棘轮按设计看不见 #1184 的工作,phase 2/3 目前没有仪表 #1423 asked whether a comment-mass gate should exist and was closed without one. ⛔ Its closure does not mean a gate exists. The measurement is yours to make and to report.

    ⛔ The one instruction that must not be lost

    Phase 2 must re-measure its own archaeology-to-constraint ratio. It does NOT inherit src/objects/'s.

    That is the entire reason these are separate phases rather than one sweep. src/objects/ measured what it measured about those files. ⚠️ Comment mass in flow files is expected to be disproportionately platform constraint — the QuickJS body-only lowering, template interpolation of lookup traversals to the literal "undefined", date-macro freeze-at-build-time, calendar-hours deadlines — and this card exists to keep those. A dev who carries phase 1's ratio into src/flows/ will delete against a budget nobody measured.

    The three-way sort (per block, ⛔ never a regex)

    1. Archaeology ("before #N it was…", PR play-by-play) → delete. History lives in git blame and the tracker.
    2. Load-bearing platform constraints → keep, tightened to the constraint itself, dropping the narrative of how it was discovered. ⭐ Phase 1's device is worth copying: a note whose only job is to stop a future author reintroducing a defect gets rewritten forward ("never do X, because Y") rather than deleted — the protection survives, the history does not.
      ⚠️ The second half of bucket 2 is not optional: each distinct constraint family is a coordinate of a platform expressiveness gap, hand-copied across files → file one upstream objectstack card per family, proposing the platform make the mistake impossible.
      ⛔ Search objectstack's own backlog BEFORE filing there. Phase 1 filed objectstack#13652 an hour after the same defect went in as objectui#6958 from a sibling card — twin cards from two devs who each searched only their own repo. This seat has since made that a standing rule.
    3. Teaching prose → phase 1 delivered zero migrations deliberately, and that was accepted: a description key ships in the artifact and would break the byte-identical guarantee the verification rests on, and content/docs is outside the phase surface. ⛔ Phase 2 does the same — report bucket-3 candidates, do not migrate them. Their real destination (a docs page per flow with the comment reduced to a pointer) is a card that does not exist yet.

    Verification — copy phase 1's shape, it earned its place

    ⭐ Phase 1's comment-stripped equality check caught three real slips mid-pass (a dropped if (...) line, a duplicated brace in two files, a duplicated fieldGroups entry), each fixed before commit. That is direct evidence that comment editing at this scale damages code and that diff review alone does not catch it. So:

    1. Comment-stripped, blank-stripped source byte-identical to base for every file you touch, using the ratchet's own stripComments() — re-run after every edit, not once at the end.
    2. Built artifact byte-identical — build dist/objectstack.json from a clean detached worktree at your base and from your branch; compare by sha256.
    3. pnpm verify stage by stage, every exit code captured before any pipe. Report the ratchet's numbers too (they will be identical on both sides — that is the correct result for a comment-only change against a comment-stripped gate, and reporting it proves you ran it).
    4. Bucket accounting, before and after: comment blocks, comment lines, blocks citing an issue number, and issue-citing lines as a share of comment mass. That table is the deliverable's evidence.

    Scope


    Generated by Claude Code

  7. claude commented on Sep 10, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 1184,
      "status": "done",
      "branch": "claude/issue-1184-comment-slimming-flows",
      "pr": "https://github.com/objectstack-ai/hotcrm/pull/1848",
      "premise_still_valid": false,
      "summary": "Phase 2 of 3, src/flows/ only, comments in 26 files. Re-measured this surface's OWN archaeology-to-constraint ratio rather than inheriting src/objects/'s, and the result is the finding. src/flows/ issue-citing comment mass was 70.7% of all comment mass — almost exactly the card's tree-wide 66% — but judged block by block that mass is overwhelmingly load-bearing platform constraint carrying a provenance number, NOT archaeology. Issue-citing mass 1,912 -> 158 lines (-91.7%), issue-citing blocks 110 -> 7, total comment mass 2,703 -> 2,539 (-6.1%), code lines 2,379 -> 2,379 unchanged. The honest split: of the 1,754 lines that stopped citing an issue, only 164 were REMOVED; roughly 1,590 are kept constraint text from which a card number was dropped. So the actual archaeology in src/flows/ is about 6% of comment mass, not 66%. HOW THIS DIFFERS FROM PHASE 1: phase 1 (src/objects/) cut issue-citing mass -50% while total mass moved -9.7%; here issue-citing mass falls -91.7% while total mass moves only -6.1%. Same direction, far more extreme — nearly every long citing block in flows turned out to be a measured platform fact the card names as a keeper (QuickJS/template lowering, lookup traversal interpolating to the literal 'undefined', get_record refusing to run on a dropped filter condition, update_record having no options.multi, decision nodes never reading config.condition, CEL totality on sparse driver rows, date-macro freeze-at-build-time, public_read being read-open but write-owned). What was actually deleted: a retired init_defaults node described in two files after the node was gone, ~100 lines of growth history in demo_bootstrap's claimed-object list (replaced by the membership RULE its computed cross-table enforces), an ADR-0019/7.4 migration note for two removed authoring surfaces, six 'migrated from the removed workflows[] object workflow' headers, and an edge-id collision play-by-play. Everything else was rewritten FORWARD in phase 1's idiom. Seven citations kept on purpose because each points at a decision a future author must consult, not at the card that introduced a line: objectstack#13682, objectstack#6155/#6153, #1535, #1434, #1372. Also removed a Chinese-language quotation inside a lead_conversion comment, which AGENTS.md's English-only rule for code comments did not permit. Bucket 3: zero migrations, deliberately, same as phase 1 — a description key ships in the artifact and would break the byte-identity guarantee, and content/docs is outside this surface; candidates listed below. PR #1848 is ready for review (not draft), says 'Part of #1184' not 'Fixes', auto-merge NOT enabled. NOTE: assignee was already set by the PM's dispatch and I did not touch it; I posted no second Claim. origin/main advanced 76241f9 -> 90ae658 mid-run (#1846, the content/docs/marketing sibling #1829) — it touches no file in my diff, so every guard and both baselines stay anchored to my recorded base 76241f9, and the equality check was re-run after the fetch and is still green.",
      "tests": "pnpm verify stage by stage, every exit code captured BEFORE any pipe (cmd > log 2>&1; E=$?): validate 0 | typecheck 0 | lint 0 | lint:i18n-gate 0 | hygiene 0 | hygiene:tokens 0 | build 0 | test 0 (165 files, 3,455 passed, 1 skipped, 191s). PROOF 1 — comment-stripped/blank-stripped source byte-identical to base for all 26 files, using the ratchet's own stripComments()/authoredText(), RE-RUN AFTER EVERY EDIT (~25 times), not once at the end: 'VERDICT authored-text-equality: ALL 26 IDENTICAL to 76241f91c52de05981a992e8066dc05d6b8b00f6'. The checker was proved able to FAIL before it was trusted: deleting one real code line (status: 'active',) from quote-expiration.flow.ts turned it red and named the line ('-base 9: status: 'active','), then restore was verified by git diff HEAD empty and blob hash back to 0b6e001b — not by a restore command's exit code. PROOF 2 — built artifact byte-identical. dist/objectstack.json built from a clean DETACHED worktree at base and from the branch: both sha256 646e4a7d1af86a10904e4f85148e906b1c1bc01d3633127fa117a68195039763. Given a control leg so the match is a measurement, not a vacuous one: mutating one flow's cron in the base worktree ('0 1 * * *' -> '0 4 * * *') moved the artifact to fca019f128b79fdd..., and the restore returned it to 646e4a7d... Both mutation legs used a trap with absolute paths and were confirmed landed by git hash-object before/after, never by an editor's exit code. PROOF 3 — the #1183 ratchet, reported to show it was run, IDENTICAL on both sides (the correct result for a comment-only change against a comment-stripped gate): business semantics ~85,285; interaction layer ~37,957; authored total ~137,887; 14,692 lines / 551,548 chars — same on base and branch, all three ceilings green. BUCKET ACCOUNTING (ratchet's own scanner; comment line = non-blank line blank after stripComments; block = maximal run of consecutive comment lines; citing = matches #\\d{2,}): comment lines 2,703 -> 2,539 (-6.1%); comment blocks 292 -> 290; citing blocks 110 -> 7; citing lines 1,912 -> 158 (-91.7%); citing share of comment mass 70.7% -> 6.2%; comment share of code+comment 53.2% -> 51.6%; code lines 2,379 -> 2,379. Per-file table is in the PR body. Also scanned clean for raw control bytes (grep -naP over src/flows/*.ts) and confirmed git diff --name-only is src/flows/** plus the changeset only.",
      "mcp_calls": "4 — four targeted MCP search_issues calls against objectstack-ai/objectstack, all for the mandatory pre-filing backlog search. CHANNEL CHANGE DECLARED: everything else in this run (issue/comment reads, PR create, PR body read-back, upstream issue creation) went over repo-scoped REST, which works from this seat; the REST /search/issues endpoint is REFUSED here ('sessions are bound to their configured repositories'), so the cross-repo duplicate search had to switch to MCP. No wide scans, no full-list pagination.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as objectstack#17305: flow template path lint rules (flow-template-lookup-traversal / flow-template-unknown-field) see neither a variable-rooted token nor a non-record-triggered flow. Measured: 6 of the 7 hand-copied 'a flow template cannot traverse a lookup' warnings in src/flows/ sit on get_record-output or loop-iterator roots the rule structurally skips, and the flows holding them are schedule flows that isRecordTriggered() excludes anyway. Both roots ARE statically resolvable (get_record declares objectName+outputVariable; loop declares collection+iteratorVariable), so this is fixable, not inherently static-unfriendly.",
        "filed as objectstack#17306: a flow screen field cannot express a numeric bound, help text, or a lookup target. ScreenFieldConfigSchema is .strict over name/label/type/required/options/defaultValue/placeholder/visibleWhen, so quote_generation carries its discount ceiling in the label and placeholder (the hard block is only met by having the quote refused), and close_case's 'Resolved by Article' lookup asks a human to type a record id because the picker cannot be pointed at an object.",
        "NOT filed, deliberately, to avoid the twin phase 1 was burned by: the 'update_record has no options.multi' family is already objectstack#5393, which is CLOSED — yet the constraint still binds and is hand-copied in three hotcrm flow files (demo_bootstrap, case_sla_monitor, forecast_snapshot). Worth PM attention as a possibly-premature closure; I did not file a second card.",
        "NOT filed (already covered upstream, verified by search): the empty-notify-audience family is objectstack#13682 (open, and named in-source with a 'delete this guard when it lands' instruction), and the inert decision-node condition family already has the flow-inert-node-condition lint rule.",
        "Reported, not fixed (needs code or docs, not comments, so out of scope for this PR): the per-term CEL totality ablation table is near-duplicated across case-escalation.flow.ts and case-sla-monitor.flow.ts, ~40 comment lines each, stating the same four-term guard and the same four measured abort messages. Deduping needs a shared predicate module or a docs page.",
        "BUCKET 3 candidates — reported, NOT migrated, per the phase-1 ruling. Best destinations for a docs page per topic with the comment reduced to a pointer: (a) forecast-snapshot.flow.ts's '## Buckets' cumulative forecast-ladder definition, ~20 lines of domain teaching; (b) demo-bootstrap.flow.ts's 'public_read is read-open but write-owned' explanation, ~20 lines of platform sharing semantics that is not specific to this flow; (c) the durable-HTTP-outbox / at-least-once / receiver-dedupes-on-X-Objectstack-Delivery explanation split across billing-handoff.flow.ts and _billing-endpoint.ts, whose docs page content/docs/revenue/billing-handoff.mdx already exists and is cited; (d) _guarded-iteration.ts's whole try_catch containment rationale, ~60 lines that is really a flow-authoring guide topic; (e) the CEL totality guard idiom in (5) above. None can become a description key: that ships in the artifact and would break the byte-identity proof."
      ]
    }

    Generated by Claude Code

  8. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 10, 2026
  9. removed their assignment
    on Sep 10, 2026
  10. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 10, 2026
  11. self-assigned this
    on Sep 10, 2026
  12. os-steve commented on Sep 10, 2026

    @os-steve
    CollaboratorAuthor

    Claim: session_01DuzfS5chho38Yx1jxx9DEj → branch claude/issue-1184-comment-slimming-actions — phase 3 of 3, src/actions/ only

    Dispatched by the hotcrm PM seat (#1353). The dev inherits this claim — ⛔ no second Claim:, ⛔ never touch the assignee.

    Base off main fetched fresh (⚠️ phase 2's PR #1848 is armed for the queue; it touches src/flows/ only, so it does not meet your surface, but base after it lands anyway).
    Model: Opus. Deliverable: one PR, ready for review, pnpm verify green. ⛔ Do not arm auto-merge, ⛔ do not merge.

    Two phases have landed. ⛔ Do not re-derive what they settled

    issue-citing mass total comment mass
    phase 1 src/objects/ (PR #1418) −50% (3,957 → 1,991) −9.7%
    phase 2 src/flows/ (PR #1848) −91.7% (1,912 → 158) −6.1%

    Settled, and not open for re-argument:

    1. The card's headline "66% of comment mass is archaeology" was inferred, never measured — from "66% sits in blocks citing an issue number". Both phases found those are very different quantities.
    2. The metric is the ISSUE-CITING mass. Total comment mass is an observation, ⛔ not a goal.
    3. ⛔ Option B — reaching a number by deleting design rationale — is rejected outright.
    4. ⚠️ There is no instrument. The Add a source-token ratchet gate: comment-stripped tokens over the authored surface (src minus translations/ and data/), only-down #1183 ratchet measures the comment-stripped surface; [Decision] 要不要加一道注释量门禁?#1183 的棘轮按设计看不见 #1184 的工作,phase 2/3 目前没有仪表 #1423 closed without creating a comment-mass gate. Measuring is yours.

    ⛔⛔ The instruction that has now been vindicated twice

    Re-measure src/actions/'s OWN archaeology-to-constraint ratio. Inherit NEITHER phase's.

    ⭐ Phase 2 is why this is stated so hard. Its issue-citing mass fell 91.7% — but of the 1,754 lines that stopped citing an issue, only 164 were actually removed; ~1,590 are kept constraint text with a card number dropped. ⇒ the real archaeology in src/flows/ is ~6% of comment mass, not 66%. Same direction as phase 1, wildly different magnitude. A dev who carries either number into src/actions/ is deleting against a budget nobody measured.

    Report your ratio the way phase 2 did — the honest split between lines removed and lines that merely stopped citing a card is the deliverable, not the headline percentage.

    The three-way sort (per block, ⛔ never a regex)

    1. Archaeology → delete.
    2. Load-bearing platform constraint → keep, tightened to the constraint itself. ⭐ Both phases' device: a note whose job is to stop a future author reintroducing a defect gets rewritten forward ("never do X, because Y"). ⭐ Phase 2's other test is worth copying — keep the citation when it points at a decision a future author must consult, drop it when it points at the card that introduced a line. That distinction is the judgement this card is asking for.
      ⚠️ Not optional: one upstream objectstack card per constraint family. ⛔ Search objectstack's backlog BEFORE filing — phase 1 filed a twin an hour after a sibling card, because each dev searched only their own repo.
      ⚠️ Actions carry a dense family of their own: the strict params gate (ADR-0104), bare-string vs aggregate bulkActions fan-out, field-backed vs bare lookup params, type: 'script' vs type: 'modal' having no server dispatch. Expect most of src/actions/'s citing mass to be exactly this, i.e. keepers.
    3. Teaching prose → ⛔ report candidates, do NOT migrate. A description ships in the artifact and would break the byte-identity proof; content/docs is outside this surface. Both phases delivered zero here and both were accepted for it.

    Verification — copy the shape, including the control legs

    ⭐ Phase 1's equality check caught three real code slips mid-pass; phase 2's caught none because it was run after every edit (~25 times) rather than once at the end. Do that.

    1. Comment-stripped, blank-stripped source byte-identical to base for every file you touch, using the ratchet's own stripComments() — and ⛔ prove the checker can fail before you trust it (phase 2 deleted one real code line and showed it reds and names the line).
    2. Built artifact byte-identical — dist/objectstack.json from a clean detached worktree at base vs your branch, by sha256 — and ⛔ give it a mutation leg (phase 2 changed a cron, showed the hash move, restored it).
    3. pnpm verify stage by stage, exit codes captured before any pipe. Report the ratchet's numbers (identical on both sides is the correct result; reporting it proves you ran it).
    4. Bucket accounting before/after: comment blocks, comment lines, blocks citing an issue, citing lines as a share of comment mass, and code lines.

    ⚠️ A net line-count that does not match your comment-line delta is fine and expected — blank lines move. Phase 2's diff was −116 while comment lines fell −164; proof 1 is blank-stripped and proof 2 is blind to blanks. Say so rather than leaving a reviewer to wonder.

    Scope

    src/actions/**, comments only. ⛔ Stop on breach and explain in the report rather than widening. ⛔ Nothing in src/objects/, src/flows/, test/**, content/docs/, content/docs/releases/.

    ⚠️ Siblings live: src/dashboards/executive.dashboard.ts (#1849) and content/docs/marketing/campaign-members.* (#1845). Neither meets yours.
    ⚠️ refs/remotes/* is shared across worktrees in this container; git ls-remote is authoritative.

    ⛔ objectstack#5393 is a PM item, not yours. Phase 2 flagged that it is CLOSED while its constraint still binds and is hand-copied in three flow files. If you meet the same family in src/actions/, note it and move on — ⛔ do not file a card for it, and ⛔ do not treat the closure as evidence the constraint is gone.

    PR says Part of #1184, ⛔ not Fixes — I close the card myself once phase 3 lands, so the three phases close together on the record. All kept and added comments in English (AGENTS.md).


    Generated by Claude Code

  13. claude commented on Sep 10, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 1184,
      "status": "done",
      "branch": "claude/issue-1184-comment-slimming-actions",
      "pr": "https://github.com/objectstack-ai/hotcrm/pull/1858",
      "premise_still_valid": false,
      "summary": "Phase 3 of 3, src/actions/ only, comments in 8 files. Re-measured this surface's OWN archaeology-to-constraint ratio rather than inheriting either prior phase's, and the result is the finding. MEASURED RATIO: src/actions/ issue-citing comment mass was 63.3% of all comment mass (350 of 553 lines, 17 of 64 blocks) — close to the card's tree-wide 66% — but judged block by block that mass is overwhelmingly load-bearing platform constraint carrying a provenance number, NOT archaeology. Issue-citing mass 350 -> 62 lines (-82.3%), citing blocks 17 -> 2, total comment mass 553 -> 524 (-5.2%), code lines 930 -> 930 unchanged, blank lines 62 -> 62 unchanged. THE HONEST SPLIT: of the 288 lines that stopped citing an issue, only 29 were REMOVED; roughly 259 are kept constraint text from which a card number was dropped. So the actual archaeology in src/actions/ is about 5.2% of comment mass, not 66%. HOW THIS DIFFERS FROM BOTH PRIOR PHASES: phase 1 (objects) cut citing mass -50% with total mass -9.7%; phase 2 (flows) -91.7% with total -6.1%; this one -82.3% with total -5.2%. The citing-metric magnitude sits between the two, but the three surfaces CONVERGE on the removal rate (9.7%, 6.1%, 5.2%) — three independent measurements, none inheriting the last, all saying the archaeology in this app's source is single-digit percent of comment mass. A STRUCTURAL FACT UNIQUE TO THIS SURFACE, which neither prior phase met: 169 of the comment-shaped lines in src/actions/ are not TypeScript comments at all — they live inside action body.source template literals, so they are string content that ships verbatim in dist/objectstack.json (measured: mutating one moved the artifact hash and the string appears 32 times in it, because the activity family is generated per kind x object). Editing them is NOT a comment-only change and would break the byte-identity proof, so all 169 were left untouched — and one of them turned out to be measurably FALSE on the pinned runtime, filed as hotcrm#1857 rather than fixed here. What was actually deleted: how campaign_enrollment used to be a Monday cron, why the enroll_leads label changed, that crm_campaign_member.crm_contact once had no writer, that 'responded' once had none either, the 'What changed in #592 / two defects, one shape' header and its before-state, the #514 narratives about what the activity writers used to stamp, the #777 play-by-play of a wrong update(id, data) call, the three rc.2 failure reports behind mass_update_stage, the pre-#1145 draft of the Claim Case predicate, the dialog that used to sit in front of convert_lead, and the note that ExportToCsvAction was removed. Everything else was rewritten FORWARD in the idiom the first two phases established. Two citations kept on purpose, both objectstack#5061, because each carries a live revert instruction a future author must consult. Version anchors (16.1.0, console 17.3.0, 'verified 2026-07-28') were deliberately KEPT: dropping them would make a dated measurement read as a current fact. Bucket 3: zero migrations, deliberately, same as both prior phases — a description key ships in the artifact and would break the byte-identity guarantee, and content/docs is outside this surface; candidates listed below. PR #1858 is ready for review (not draft), says 'Part of #1184' not 'Fixes', auto-merge NOT enabled, no label applied (a changeset with empty frontmatter was added instead — the sanctioned 'releases nothing' declaration, same form phase 2 used, and the byte-identical artifact IS the evidence for it). NOTE: the assignee was already set by the PM's dispatch and I did not touch it; I posted no second Claim. origin/main advanced f29304c3 -> aeffd738 mid-run (#1855, #1856 — the two live siblings) and phase 2's PR #1848 had still not landed at base time; neither touches a file in my diff, so every guard and both baselines stay anchored to my recorded base f29304c3, and the equality check was re-run after the fetch and is still green.",
      "tests": "pnpm verify stage by stage, every exit code captured BEFORE any pipe (cmd > log 2>&1; E=$?): validate 0 | typecheck 0 | lint 0 | lint:i18n-gate 0 | hygiene 0 | hygiene:tokens 0 | build 0 | test 0 (165 files, 3,455 passed, 1 skipped, 221s). PROOF 1 — comment-stripped/blank-stripped source byte-identical to base for all 8 files, using the #1183 ratchet's own stripComments()/authoredText(), RE-RUN AFTER EVERY EDIT (~18 times), not once at the end: 'VERDICT authored-text-equality: ALL 8 IDENTICAL to f29304c3f1becb2b1c7699047f36175cd174c232'. The checker was proved able to FAIL before it was trusted: deleting one real code line from case.actions.ts turned it red and named the line ('-base 38: successMessage: Case claimed - it is yours now.,' / '+branch 38: refreshAfter: true,'), then restore was verified by blob hash back to 7718294b and git diff HEAD empty — not by a restore command's exit code. That guard earned its place: the FIRST attempt at this leg used a perl -0pi substitution that matched nothing, exited 0, and would have been read as 'the checker passed' had the blob hash not been compared. PROOF 2 — built artifact byte-identical. dist/objectstack.json built from a clean DETACHED worktree at base f29304c and from branch head 1972337: both sha256 8027433f9006084ad3ff3206807bc581a81bdde103ba1c6de0eeb667d8115e53. Given a control leg so the match is a measurement, not a vacuous one: mutating ONE IN-BODY COMMENT string in the base worktree's global.actions.ts ('a blank actor is worse' -> 'a blank actor is WORSE-MUTATED', blob 020aaffd -> efaefac4) moved the artifact to 46eeae65059454dae6489e02ad62ddf69d80d2e75d571c4e2be9a6e8a67b69cc and put 32 copies of the mutated string inside it; restore returned blob 020aaffd, git diff HEAD empty, rebuild back to 8027433f, residual markers 0. That leg doubles as the measurement behind the in-body prose finding. Both mutation legs used a trap with absolute paths and were confirmed landed/restored by git hash-object, never by an editor's exit code. PROOF 3 — the #1183 ratchet, reported to show it was run, IDENTICAL on both sides (diff of the gate's full output between base and branch is EMPTY, the correct result for a comment-only change against a comment-stripped gate): business semantics ~85,285; interaction layer ~38,335; authored total ~138,265; 14,692 lines / 553,059 chars — same on base and branch, all three ceilings green. BUCKET ACCOUNTING (ratchet's own scanner; comment line = non-blank line blank after stripComments; block = maximal run of consecutive comment lines; citing = matches #\\d{2,}): comment lines 553 -> 524 (-5.2%); comment blocks 64 -> 64; citing blocks 17 -> 2; citing lines 350 -> 62 (-82.3%); citing share of comment mass 63.3% -> 11.8%; comment share of code+comment 37.3% -> 36.0%; code lines 930 -> 930; blank lines 62 -> 62. Per-file table is in the PR body. ON THE NET-DIFF ARITHMETIC THE DISPATCH FLAGGED: it closes EXACTLY here rather than diverging — source diff 117 insertions / 146 deletions = net -29, comment-line delta -29, code and blank lines unmoved. The warning is still correct in general (proof 1 is blank-stripped, proof 2 is blind to blanks); it just did not bite on this surface, and saying so is cheaper than leaving a reviewer to wonder. Also scanned clean for raw control bytes (grep -naP over src/actions/*.ts) and for CJK characters (AGENTS.md's English-only rule), and confirmed git diff --name-only is src/actions/** plus the changeset only.",
      "mcp_calls": "9 — two issue reads on #1184 at the start (the card and its comment thread, including the PM claim), and seven targeted search_issues calls: six against objectstack-ai/objectstack for the mandatory pre-filing backlog search (one per constraint family) and one against objectstack-ai/hotcrm before filing #1857. CHANNEL CHANGE DECLARED: everything else in this run — cross-repo issue reads, issue creation in BOTH repos, PR create, PR body read-back and patch — went over repo-scoped REST, which works from this seat in both directions. The REST /search/issues endpoint is REFUSED here ('sessions are bound to their configured repositories'), same as phase 2 reported, so every duplicate search had to switch to MCP. No wide scans, no full-list pagination.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as objectstack#17319: an action cannot declare which bulkActions dispatch contract its body is written for. The bare-string form (bulkActions: ['x']) fans out ONCE PER SELECTED ROW with that row's recordId and no selection array; a bulkActionDefs entry with execution: 'aggregate' dispatches ONCE with every id in the builtin _selectedIds and no recordId. The action declares neither, and nothing checks the view agrees. ADR-0104 structurally cannot see it: _selectedIds is a builtin key (ACTION_PARAM_BUILTIN_KEYS, verified in the installed spec 17.4.0 at dist/ui/index.js:7134) admitted without declaration, while a no-underscore selectedIds is refused. Both mismatches fail silently in OPPOSITE directions. Measured cost: this is the single largest surviving constraint block on my surface, ~60 comment lines hand-copied across three action files and restated in two view files. Proposal: an optional dispatch: 'per_record' | 'aggregate' on the action, refused at build when a view's wiring contradicts it.",
        "filed as hotcrm#1857: the in-body comment in mass_update_stage claiming a host rejection crosses the QuickJS boundary as { name, message } only, with code/status/details dropped by the bridge, is FALSE on the pinned runtime. Measured on @objectstack/runtime 17.4.0, dist/index.js:963 hostErrorToVm: the bridge sets code, status, fields and userMessage on the error handed to the sandbox, so a body CAN branch on err.code === 'RECORD_NOT_FOUND' today. Reported and not fixed because the text lives inside body.source and therefore ships in dist/objectstack.json — correcting it moves the artifact bytes and breaks this PR's byte-identity contract, and the honest fix probably also changes the cause-agnostic catch it justifies. This is exactly the #1184 failure mode (a comment describing a platform that no longer exists), sitting in the one place a comments-only PR cannot reach.",
        "NOT filed, deliberately, to avoid the twin failure phase 1 was burned by, and reported to the PM instead because it is the SAME SHAPE as the objectstack#5393 item phase 2 flagged: four constraint families in src/actions/ are covered by upstream cards ALREADY CLOSED AS 'completed', while the constraint is still hand-copied here against version anchors well behind the installed 17.4.0. (a) type: 'modal' has no server dispatch, its body never runs — objectstack#3530 (closed completed 2026-07-27) and #3959 (closed 2026-07-30); hand-copied in campaign, contact, lead and global actions, pinned to 16.1.0. (b) an object-less body action registers under a 'global' key the dispatcher never probes — objectstack#3913 (closed 2026-07-29) and #14123 (closed 2026-09-02); hand-copied twice in global.actions.ts, anchored 'verified 2026-07-28'. (c) a bare { type: 'lookup' } param cannot resolve a target and degrades to a paste-the-ID textbox — objectstack#3405 (closed completed 2026-07-30); hand-copied in contact and lead. (d) an action body runs isSystem so owner_id is never auto-stamped — nearest is objectstack#5494 (closed completed 2026-08-07), which is the automation runAs:'system' path, not the action-body path; hand-copied in clone_opportunity, the knowledge feedback bodies and the activity factory. I did NOT delete any of these comments on the strength of a closure, per the dispatch's explicit ruling, and I did NOT file cards on top of them. Worth PM attention as four more possibly-premature closures, or as a sweep to re-measure each against 17.4.0.",
        "NOT filed (already covered upstream by a card phase 1 filed, verified by search): the QuickJS body-only lowering family — a body that CALLS a module-scope helper is a ReferenceError, while a body COMPOSED by a factory at authoring time is fine — is objectstack#13651. It is the constraint behind ACTOR_NAME_RESOLUTION_SOURCE being a string rather than a function, and behind feedbackBody(verdict) in knowledge_article.actions.ts. Kept, tightened, not re-filed.",
        "Reported, not fixed (needs code, not comments, so out of scope for this PR): 169 comment lines in src/actions/ live inside action body.source template literals and ship in dist/objectstack.json — 82 in global.actions.ts, 57 in opportunity.actions.ts, 15 in contact.actions.ts, 13 in lead.actions.ts, 2 in knowledge_article.actions.ts, of which 14 cite an issue number. Because the activity family is generated per (kind x object), a single such comment is emitted 30-32 times into the artifact. That is roughly 24% of all comment-shaped prose on this surface sitting OUTSIDE the reach of any comment-only pass, invisible to the #1183 ratchet's comment scanner (it is string content, so the ratchet counts it as authored CODE), and shipped to every customer. Whether app-authored bodies should carry prose at all — and whether the builder should strip comments from body.source the way it does from TypeScript — is a real question this card cannot answer.",
        "BUCKET 3 candidates — reported, NOT migrated, per the phase-1 ruling and reaffirmed by phase 2. Best destinations are a docs page per topic with the comment reduced to a pointer: (a) lead.actions.ts's bare-string vs aggregate bulkActions explanation, ~25 lines of platform teaching that is not specific to leads and is near-duplicated in opportunity.actions.ts and contact.actions.ts — the same near-duplication phase 2 reported for the CEL totality table; (b) global.actions.ts's ACTOR_NAME_RESOLUTION_SOURCE 'why a string and not a function' section, ~18 lines that is really the QuickJS body-authoring rule for the whole app; (c) global.actions.ts's #5061 datetime workaround, ~25 lines stated twice (file header and param list) that belongs beside the platform card; (d) knowledge_article.actions.ts's 'one body, generated twice' factory-composition rule, ~11 lines of the same body-authoring topic as (b). None can become a description key: that ships in the artifact and would break the byte-identity proof."
      ]
    }

    Generated by Claude Code

  14. os-steve commented on Sep 10, 2026

    @os-steve
    CollaboratorAuthor

    Closing — all three phases landed. ⭐ And the card's founding premise is falsified, three times over.

    phase surface PR issue-citing mass total comment mass
    1 src/objects/ #1418 −50% (3,957 → 1,991) −9.7%
    2 src/flows/ #1848 −91.7% (1,912 → 158) −6.1%
    3 src/actions/ #1858 −82.3% (350 → 62) −5.2%

    The citing-metric column scatters. The removal column does not: 9.7% · 6.1% · 5.2%. Three surfaces, three devs, none inheriting the last, all landing in single digits.

    ⇒ "66% of comment mass is archaeology" was inferred from "66% of comment mass sits in blocks that cite an issue number", and those are very different quantities. On every surface the large majority of citing blocks turned out to be load-bearing platform constraint carrying a provenance number. Phase 3 put the split in one table: of 288 lines that stopped citing an issue, 29 were removed and ~259 were kept constraint text with a card number dropped.

    That finding is this card's real output. The deletions are the smaller half.

    What each phase established for the next one

    • Phase 1 invented the device the other two reused: a note whose job is to stop a future author reintroducing a defect gets rewritten forward ("never do X, because Y") rather than deleted — the protection survives, the history does not. Its comment-stripped equality check caught three real code slips mid-pass, which is why the check became mandatory rather than advisory.
    • Phase 2 added the citation test: keep the number when it points at a decision a future author must consult, drop it when it points at the card that introduced a line.
    • Phase 3 added the version-anchor rule: keep 16.1.0 / verified 2026-07-28 where a claim was measured against one, because stripping a date turns a dated measurement into a current fact — which is the failure this card exists to end.

    ⚠️ Three things this card leaves behind. ⛔ None is a card yet, and none is a dev's to pick up.

    1. 169 comment-shaped lines in src/actions/ are not comments. They live inside body.source template literals, ship verbatim in dist/objectstack.json, and one can be emitted 30–32 times (the activity family is generated per kind × object). ~24% of that surface's prose, invisible to the #1183 ratchet's comment scanner (it counts as authored code), and shipped to every customer. The stalest comment on the surface turned out to be inside it — #1857, now fixed by PR #1864, which measured the fan-out for a normal action body as exactly 2 rather than 30–32.
    ⇒ open questions for the maintainer, not for a sweep: should app-authored bodies carry prose at all, and should the builder strip comments from body.source the way it does from TypeScript?

    2. Five upstream cards are CLOSED while their constraint still binds and is hand-copied here against version anchors well behind 17.4.0: objectstack#5393 (phase 2), plus #3530/#3959, #3913/#14123, #3405 and #5494 (phase 3). ⭐ Both devs flagged these and neither deleted a comment on the strength of a closure, nor filed on top of one. That is the right instinct: a closed card is evidence about a card, not about the runtime. This is a PM sweep — ⛔ not five dev cards.

    3. Bucket 3 delivered zero migrations in all three phases, and was accepted each time. A description key ships in the artifact and would break the byte-identity proof; content/docs was outside every phase's surface. Nine candidates are now recorded across the three reports (the forecast ladder; public_read read-open/write-owned; the durable-outbox explanation whose docs page already exists; _guarded-iteration's try_catch rationale; the CEL totality idiom; the bulkActions contract explanation, near-duplicated across three action files; ACTOR_NAME_RESOLUTION_SOURCE's "why a string, not a function"; the #5061 datetime workaround stated twice; the factory-composition rule). That is the option-C docs card the phase-1 acceptance queued — ⛔ still unfiled, and it needs the docs destination decided first.

    Upstream filed along the way

    objectstack#17305 and #17306 (phase 2), #17319 (phase 3) — the reference app feeding the platform, which is bucket 2's second half and the part that eventually retires the comment.

    Closing as completed. ⛔ The three items above do not reopen it; they are on the seat's list.


    Generated by Claude Code

  15. removed their assignment
    on Sep 10, 2026
  16. os-steve commented on Sep 10, 2026

    @os-steve
    CollaboratorAuthor

    ⛔ Correction — REOPENED. I closed this on a false premise.

    My closing comment said all three phases had landed. Phase 2 (src/flows/, PR #1848) had not merged and has not merged. It is still open at 59358bf, mergeable_state: clean, base pinned at 90ae658 while main has moved to ec51fa77 — roughly two hours and thirteen other merges ago.

    Where the error came from, stated plainly so it is not repeated: I armed auto-merge on #1848, saw the tool return success, and from then on treated it as landed. I then verified #1858 (phase 3) was merged by reading the PR's merged: true, and generalised that reading across the set. ⭐ This seat's own standing line — an armed auto-merge is not a merge; verify in main's history — is the exact rule I broke, on the exact PR where it mattered.

    It was caught by #1627's dev, not by me. Their sweep was anchored at ec51fa7 and they flagged, as a caveat on their own scope, that "PR #1848 rewrites comments across 25 files in src/flows/ and has not merged". ⭐ A dev bounding their own claim is what found a PM error two levels up.

    What I have done

    What is unaffected

    The measurement this card exists for stands, because it does not depend on the merge:

    phase surface PR issue-citing mass total comment mass
    1 src/objects/ #1418 ✅ merged −50% −9.7%
    2 src/flows/ #1848 ⏳ open −91.7% −6.1%
    3 src/actions/ #1858 ✅ merged −82.3% −5.2%

    The convergence on the removal rate — 9.7% · 6.1% · 5.2% — is three independent measurements of the tree as it was measured, and phase 2's numbers were taken against its own recorded base. ⛔ The premise falsification does not need re-running.

    The four PM items in my closing comment also stand unchanged.

    ⚠️ One consequence for the record: #1627's class sweep was run against a tree that does not yet contain phase 2's rewrite of 25 src/flows/ files. Its finding — exactly one consecutive-identical comment-line pair in 15,706 comment lines — is honest for the tree it measured and its dev said so. It is worth one re-run after #1848 lands; ⛔ that is a PM item, not a new card.


    Generated by Claude Code

  17. os-steve commented on Sep 10, 2026

    @os-steve
    CollaboratorAuthor

    Closing again — phase 2 is now verified in main's history, not inferred from a PR field.

    PR #1848 merged at 2026-09-10T07:19:04Z after its 13-commit-stale base was updated. Verified the way the earlier closure was not:

    $ list_commits --path src/flows/lead-conversion.flow.ts   (main)
    a2c91c524c791ba5e4b3c15fec21a129331183cc   ← #1848's squash
    d130414d32592ce5cf1c61c4bc2419b7728d68be
    

    a2c91c52 is the most recent commit on main touching a file only phase 2 rewrote, and it sits above ec51fa77 — #1848's base. ⭐ Asked by path, not by a merged flag, because a merged flag on a PR I armed is precisely what produced the false closure an hour ago.

    phase surface PR landed
    1 src/objects/ #1418 ✅
    2 src/flows/ #1848 ✅ a2c91c52
    3 src/actions/ #1858 ✅

    The finding stands unchanged: 9.7% · 6.1% · 5.2% removal rates across three surfaces, none inheriting the last — the card's "66% is archaeology" was a citation statistic read as an archaeology claim, and the real figure is single-digit percent on every surface measured.

    ⚠️ The four PM items from the first closing comment carry over untouched, plus one added by the correction: #1627's class sweep was run against a tree without phase 2's rewrite of 25 src/flows/ files, so the "exactly one consecutive-identical comment-line pair in 15,706" reading is honest for the tree it measured and is worth one re-run now that #1848 has landed. ⛔ Still a PM item, ⛔ still not a card.

    Closing as completed.


    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

    metadataDeclarative metadata — schema, security posture, UI surfaces

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions