Skip to content

[Decision] analytics field gate (#20917): an authored cube member whose sql is an expression — keep the stand-down, judge its identifiers, refuse it, or retire expressions #20943

Closed

Description

@objectstack-fleet

Ruled: 5921156712 · letter D · 2026-09-30T23:01Z

Filed by the domain:services seat (#6021, session session_01XY5uCwTjZj7884yYtyur4H) as a decision card. #20917's dev left an open_questions entry that triage's direction does not settle: report 5919006124 on #20917, accepted in 5919159814, and escalated by the at-tier review 5919318329. #20917 has closed, so this card is the question's anchor. ⛔ Not a claim.

⚠️ Disclosure discipline (security family). This card and its comments carry no request body, header, field spelling or returned value.

The question

PR #20931 (landed as 1571aedc) judges every member an analytics query names against the caller's readable fields, at the analytics door and before either strategy runs. A member of an authored cube whose sql is an expression (for example a CASE WHEN … or a ratio of aggregates) names no single field the gate can attribute.

  • What happens today, pinned: the gate stands down on such a member.
  • As a result: an expression that reads a field the caller may not read is still answered on the native-SQL strategy. The ObjectQL strategy refuses expression measures outright.
  • The permission rule itself is unchanged for every other position.

This is a permission-boundary question, so it is the maintainer's. Four options follow; the analysis is in Chinese in the next comment.

  • A. Keep the stand-down: an authored expression is the cube author's declared derived value, like a formula field.
  • B. Judge the expression's identifiers against the object's declared fields, and refuse when any is unreadable.
  • C. Refuse an expression member for any caller who cannot read at least one field of the object.
  • D. Retire raw expressions in a cube member's sql at the contract (the spec lane), per ADR-0021's "zero raw expressions". A derived value then has to be declared in a form the platform can judge.

Governing text


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20943,
    "status": "done",
    "branch": "claude/issue-20943-cube-sql-identifiers",
    "pr": "#20998",
    "session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 (mode:subagent, the dispatching session's id)",
    "premise_still_valid": true,
    "summary": "Ruling D landed as a value narrowing: MetricSchema.sql and DimensionSchema.sql admit exactly the accept set the execution parameters name (a bare identifier, a dotted identifier path, and ''), as a .regex() so the published JSON Schema carries it as a pattern. Every other value (CASE, aggregate, ratio, quoted or $-prefixed, empty) is refused at measures.METRIC.sql / dimensions.DIMENSION.sql with invalid_format and a prescription naming the ADR-0021 dataset form; the neighbouring filters guidance, the metric-filters-removed summary, its D3 entry and its step-18 fragment no longer offer folding a condition into the metric's own sql. The kit is: the D3 entry cube-member-sql-expression-retired and its step-18 rationale fragment (registry regenerated after merging a main containing #20953, c6b3a01), the analytics_cube ledger rows re-verified live with the narrowing recorded, the regenerated data/analytics reference page, a BREAKING changeset with the ADR-0087 marker, and a new spec pin file. The showcase done_rate moved to showcase_task_metrics as done_count (count, filter status done) and done_rate (ratio over done_count and task_count, format 0.0%); the fork clause did not fire, and the measure-level filter was verified in code, in unit pins and on a live showcase boot before relying on it.",
    "tests": "Final head fa979c5 unless stated. @objectstack/spec: vitest --project local src/ 544 files 16268 passed 1 todo; --project local scripts/ 41 files 950 passed 1 skipped (at a32fd12, no packages/spec/scripts change since); --project repo on the 10 relevant files (step18-rationale-merge, conversions-major18-merge, liveness/evidence, liveness/proof-registry, cube-member-inner-name-retirement, cube-refresh-key-retirement, retired-key-migrate-sentence, root-index, file-description, category-title) 300 passed; typecheck exit 0. @objectstack/service-analytics: test 149 files 3449 passed; typecheck exit 0 (edited test in the program per --listFiles). @objectstack/example-showcase: closure rebuilt (60 tasks) then vitest 29 files 387 passed; typecheck exit 0 (3 edited files in the program). check:generated: all 15 artifacts up to date (only content/docs/references/data/analytics.mdx moved; authorable-surface, api-surface, json-schema.manifest byte-identical). dispatch-gates --commands --repo objectstack-ai/objectstack: 116 families derived, 116 run with exit codes, --ran reconciliation 116 derived / 116 run / 0 NOT-MEASURED / 0 UNRUN; 115 exit 0 (incl. check:generated, check:liveness, check:adr-0087-registration reading 'registered cube-member-sql-expression-retired', check:changeset-no-major, check:empty-changeset, check:doc-authoring, check:nul-bytes); check:platform-checklist exit 1 on areas/identity-auth.json anchor plugin-auth auth-plugin.ts#twoFactor, which this diff does not touch (branch bytes for plugin-auth, docs/qa/platform-checklist and the two checker scripts equal merge base 05be352) and which reproduces exit 1 on a detached origin/main dfe5a08 worktree. Lint, declared narrowing: eslint --no-inline-config --format json over the 10 changed lintable files → 10 files 0 errors 0 warnings; population from eslint.config.mjs's **/
    .{ts,tsx,mts,cts,js,jsx,mjs,cjs} block; invariance: the config enables no type-aware linting, so no untouched file's verdict can move. Ablations (scripts/ablation-replace.mjs, anchor hit 1, disk-verified, restore = blob hash equal to HEAD + empty git diff HEAD, trap in each script): A1 pattern admits anything → new spec pin 9 failed / 6 passed; A2 filters guidance re-offers the sql channel → 1 failed; A3 D3 id renamed in the generated region → 1 failed; A4 showcase done_count loses its filter → gap-fill 1 failed; A5 pattern admits anything + @objectstack/spec rebuilt → ablation-dist-preflight marker present in 20 built files, service-analytics parse assertion failed (expected true to be false); restore + rebuild → marker absent from all 230 files, tree clean, 14/14 green. Expected direction 'turns red' in all five, observed. Live: ephemeral showcase boot (pnpm dev -- --fresh, private port, stopped by recorded PID group) → the dataset door's done_count and done_rate reconciled with the raw showcase_task rows per priority bucket and in the ungrouped total; the column carried format 0.0% and percentScale fraction; the cube meta listed its three remaining measures. CI: not awaited (in_progress at report time).",
    "mcp_calls": "0 — no MCP GitHub tool was called",
    "api_writes": "3 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #20998, run 36799442692, read-back 12555 bytes identical); (2) label-write --assign os-justin → POST /repos//issues/20998/assignees (run 36799501475, read-back matches; zero label writes, none named by the dispatch and skip-changeset not applicable); (3) this os-dev-report → POST /repos//issues/20943/comments. git push is not counted here. Reads were single-card REST GETs.",
    "open_questions": [
    {
    "question": "AggregationMetricType 'number' / 'string' / 'boolean' existed to carry a SQL expression ('Custom SQL expression returning …'). With sql now a column reference, a measure of those types makes the raw-SQL path emit the column unaggregated and the ObjectQL path refuse it. Retire the three types?",
    "options": [
    "A: a follow-up retirement card in the same family (enum-value narrowing in spec plus the EXPRESSION_METRIC_TYPES partition in service-analytics), sequenced with #20965",
    "B: leave them until a cube expression capability is decided again"
    ],
    "recommendation": "A, because after ruling D the three types have no admissible meaning; it touches service-analytics, so it belongs with the #20965 lane rather than this spec card."
    },
    {
    "question": "'' is admitted on a dimension and on a non-count measure, as the execution parameters name '' for both members; neither ever produced a working query. Narrow it to count measures?",
    "options": [
    "A: leave as ruled",
    "B: narrow later; it needs a cross-field rule, which the dropped-refinement gate makes a JSON-Schema gap to ledger"
    ],
    "recommendation": "A for this card (the parameters are binding); B can ride option A of the previous question if the seat wants it."
    },
    {
    "question": "Conflict noted, not resolved silently: the claim's file surface lists one service-analytics test only, while the dev contract says published text that this change makes false is fixed in the same round, and it outranks the dispatch words. The service-analytics README paragraph that prescribed folding a condition into the metric's own sql was corrected here, with a patch line in the changeset. Keep it, or move it to #20965?",
    "options": [
    "A: keep it in #20998",
    "B: drop the README hunk and the patch line, and let #20965 carry it"
    ],
    "recommendation": "A, because the text ships in the package and #20965 is Blocked-by this card, so B leaves it false for at least one landing."
    }
    ],
    "out_of_scope_findings": [
    "class: b · reach: exception: release-text (a published skill teaches the retired shape) · evidence: skills/objectstack-ui/rules/dashboards.md ≈:78 escalates past a dataset to 'a hand-authored Cube (raw SQL / explicit joins)' and ≈:70 lists 'any custom-SQL metric' as the dataset gap; both are now false (member sql is a column reference; joins were already derived). Seam: spec:data/analytics.zod.ts MetricSchema.sql → consumer: none (skill prose). Governed Tier H, so not mixed into #20998 · dedupe words: dashboards skill raw SQL cube, custom-SQL metric escalation, hand-authored cube escape hatch · carrier: the separate Tier H docs PR the ruling names for ADR-0021's dated note",
    "carrier: 承接者:无 · noted, not filed: docs/qa/platform-checklist/areas/dashboards.json (the cube meta item, ≈:976, :991, :1037) names done_rate among 'exactly its four measures' and as a variant; the cube now declares three. No gate reads that text. In the PR's Acceptance notes.",
    "carrier: 承接者:无 · noted, not filed: pnpm check:platform-checklist exits 1 on origin/main (dfe5a08) and on this branch alike — areas/identity-auth.json cites packages/plugins/plugin-auth/src/auth-plugin.ts#twoFactor and the symbol is absent. The gate is out of per-PR CI by maintainer decision (lint.yml comment), so nothing here measured a public door. Dedupe words: identity-auth checklist twoFactor anchor, ABSENT SYMBOL auth-plugin",
    "carrier: 承接者:无 · noted, not filed: the cube-level CubeSchema.sql still describes itself as 'Base SQL statement or Table Name', but every reader treats it as an object name (NativeSQL quotes it as the FROM identifier; the field gate stands down when it is not a bare identifier). It is outside ruling D (members only) and was not measured through a door."
    ]
    }


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling on report 5922668750 · 2026-10-01T01:11Z

    domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1), holder of claim 5921298734. ⚠️ The disclosure discipline holds.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20943,
    "status": "done",
    "branch": "claude/issue-20943-cube-sql-identifiers",
    "pr": "#20998",
    "session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 (mode:subagent, the dispatching session's id)",
    "premise_still_valid": true,
    "summary": "Patch round for the red 'Type Check · consumer gates' job on #20998: check-i18n-coverage grew 414 → 416 on examples/app-showcase. os lint named the two keys, translations.zh-CN.datasets.showcase_task_metrics.measures.done_count.label and .done_rate.label: the two dataset measures this PR added. New head e6b66ec, append-only on fa979c5, no merge needed. The commit adds a datasets.showcase_task_metrics.measures block to the showcase bundle for both claimed locales (en: Done Tasks / Done Rate; zh-CN: 已完成任务 / 完成率), in the bundle's translated-at-birth style. The repo's scaffolder (node scripts/check-i18n-bundles.mjs --write) was run first: it regenerated its 9 packages byte-identically and does not walk examples/, so the showcase entries are hand-written as that bundle always is. os lint now reports 414 i18n issues on the showcase with no done_* key.",
    "tests": "Head e6b66ec. pnpm check:i18n-coverage exit 0 ('OK (13 config(s), 621 baselined untranslated string(s), none new)'); pnpm check:i18n exit 0 ('OK (9 package(s) — all bundles in sync'). @objectstack/example-showcase: vitest 29 files 387 passed; typecheck exit 0, with the translations file in its program (--listFiles). The families dispatch-gates derives for the translations path were run: 35 families plus the two i18n gates, 37 commands. All exit 0. check:dual-build-cjs-loads first answered exit 3 (PREREQUISITE NOT MET: 8 unbuilt packages in the recreated worktree), and after a cached turbo build of those 8 it answered exit 0. The full derivation for the whole diff is unchanged at 116 families, and the earlier 116-family run stands for the unchanged paths.",
    "mcp_calls": "0",
    "api_writes": "1 — this comment, POST /repos//issues/20943/comments via the fleet-write relay (git push not counted)",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: gate-derivation gap (reported, not fixed) · reach: named producer: dispatch-gates --commands derived 116 families for this diff and none was check:i18n-coverage, although CI's consumer-gates job runs it on every PR and it went red on this PR's own showcase edit · evidence: dispatch-gates --residue places check:i18n-coverage in the residue. Its declared literals are scripts/i18n-coverage-baseline.json, 'i18n' and 'node_modules' (both dead leads), plus the CLI paths inherited from cli-build-prerequisite.mjs. Its population roots EXAMPLES_DIR = 'examples' and PACKAGES_DIR = 'packages' are deliberately undeclared: the comment at scripts/check-i18n-coverage.mjs ≈:249-258 records REFUSE-UNSPELLABLE, reasoning that the example population is a filename filter, one objectstack.config.ts per example (3 of 240 files). That premise undercounts what the gate reads. It runs os lint on each config, which loads everything the config imports (datasets, translation bundles, objects, dashboards), so an edit anywhere in an example's source tree moves its verdict while the derivation stays silent. Possible remedy, for whoever owns the derivation: declare examples/ as a subtree (ROOT_DIR_WATCH_HINTS idiom) for this gate, where most files are config imports, or teach the derivation the config import closure · dedupe words: dispatch-gates i18n-coverage residue, examples subtree watch hint, REFUSE-UNSPELLABLE i18n coverage, consumer gates derivation gap · carrier: 承接者:无"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20943,
    "status": "done",
    "branch": "claude/issue-20943-cube-sql-identifiers",
    "pr": "#20998",
    "session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 (mode:subagent, the dispatching session's id)",
    "premise_still_valid": true,
    "summary": "Cut round for the contract-review FAIL (5923211985) at e6b66ec. New head ac70996, append-only, one commit touching two files; the schema is unchanged.\n(1) The analytics_cube dimensions.sql note no longer says '' is refused at parse. It says the schema admits '' on a dimension (the accept set ruling D's execution parameters name for both members; never a working query; narrowing it is #21000's), and that a SQL expression, a CASE bucket included, is refused.\n(2) The measures.sql note says '' is admitted on any measure, with the count-only boundary left to #21000.\n(3), optional, done: the schema comment in analytics.zod.ts now says ObjectQLStrategy#resolveMeasureAggregation refuses only the number / string / boolean partition (EXPRESSION_METRIC_TYPES) and forwards an expression under an aggregate type as a field name, which fails downstream.\nOne addition, declared: the same measures.sql note also said 'every admitted value resolves to a field the gate can judge'. That is false for '', so it now reads 'every admitted value other than '*' (which reads no field value)'. It is the same falsity as cut (2), in the sentence being rewritten.",
    "tests": "Head ac70996.\n- pnpm --filter @objectstack/spec run check:liveness: exit 0.\n- pnpm --filter @objectstack/spec check:generated: exit 0, 'All 15 generated artifacts are up to date' (spec rebuilt first).\n- pnpm --filter @objectstack/spec typecheck: exit 0.\n- Spec tests for the touched files: vitest --project local src/data/ gave 109 files, 3671 passed, 1 todo. --project repo on scripts/liveness/evidence, scripts/liveness/proof-registry, cube-member-inner-name-retirement and cube-refresh-key-retirement gave 4 files, 122 passed.\n- node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, whole branch diff: exit 0, the same 116 families as before.\n- The same derivation over this round's two paths: exit 0, 73 families. All 73 ran, and --ran reconciled them as 73 derived / 73 run / 0 NOT-MEASURED / 0 UNRUN.\n- 72 of the 73 exit 0. Three first answered exit 3 (PREREQUISITE NOT MET in the recreated worktree): check:doc-formula-expressions, check:dual-build-cjs-loads and check:lean-entry-closure. Each answered exit 0 after the workspace build (turbo build excluding docs, 72 tasks).\n- The 73rd, check:platform-checklist, exits 1 on main's identity-auth.json twoFactor anchor, unchanged from the earlier report and not this diff's.",
    "mcp_calls": "0",
    "api_writes": "1 — this comment, POST /repos//issues/20943/comments via the fleet-write relay (git push not counted)",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #20998 → 5d5e679873; the card is reopened for its governed half

    domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1) · 2026-10-01. Holder of claim 5921298734. ⚠️ The disclosure discipline holds.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20943,
    "status": "done",
    "branch": "claude/issue-20943-adr0021-note",
    "pr": "#21071",
    "session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 (mode:subagent, the dispatching session's id)",
    "premise_still_valid": true,
    "summary": "Ruling D's governed half, opened as draft PR #21071 (Part of #20943, Clause-②: no). Head 68593d4, base 6073bb9, three files, +18 / -11, no packages/** change. ADR-0021: a dated note at the end of D1 records ruling D and names MetricSchema / DimensionSchema sql in packages/spec/src/data/analytics.zod.ts, with symbol anchors. Dashboards skill: the Level A table drops "or any custom-SQL metric". The iron rule now escalates to a stored field (a summary rollup, or a field holding the computed value or bucket) or app code, not to a Cube, because a cube member's sql is a column reference. It also no longer names a formula field (declared below). Checklist: dashboards.cube-query revision 3 drops done_rate. The skill file has the same line and token counts as before, and nothing else in ADR-0021 needed an edit.",
    "changes": [
    "docs/adr/0021-analytics-dataset-semantic-layer.md (+2): one blockquote "Note (2026-10-01) — cube members", placed after D1's RLS paragraph and before D2. Rests on: ruling 5921156712 (letter D, maintainer 2026-09-30); PR #20998 = 5d5e679; analytics.zod.ts at head: CUBE_MEMBER_SQL :240 (the accept set: identifier, dotted path, ''), MetricSchema :282 with sql :330, DimensionSchema :367 with sql :394 (refused at parse: cube-member-sql-column-reference.test.ts :129-160 and :180-232); the measure refusal CUBE_METRIC_SQL_EXPRESSION_REFUSED :248-258 names the dataset measure filter and derived { op, of }; the dimension refusal CUBE_DIMENSION_SQL_EXPRESSION_REFUSED :260-265 says to keep the bucket as a field of the object. The links use #MetricSchema / #DimensionSchema / #CUBE_MEMBER_SQL symbol anchors: check:adr-symbol-anchors exit 0, check:adr-links exit 0.",
    "skills/objectstack-ui/rules/dashboards.md (5 lines rewritten, 0 net): (1) :70, "or any custom-SQL metric" removed from the escalate column. The one such metric in the tree (showcase done_rate) is now a dataset measure-filter count plus a derived ratio (chart-gallery.dataset.ts :34-38), so the row was false. (2) :75-81 iron rule: "escalate to a hand-authored Cube (raw SQL / explicit joins), a stored rollup or formula field on the object" became "escalate to a stored field on the object (a summary rollup, or a field holding the computed value or bucket) or app code, not to a Cube: a cube member's sql is a column reference". Rests on: MetricSchema/DimensionSchema sql regex :330/:394; summary has a numeric column (driver-sql sql-driver.ts :19438); the dimension refusal text :264. Declared beyond the retirement: "formula field" is dropped because the code stores no formula column (sql-driver.ts :19561-19562 "Virtual — no column"; aggregate-field-type-compatibility.ts :80 refuses sum/avg/min/max over formula, enforced by dataset-compiler.ts assertAggregateFieldTypeCompatible :389 / :713). The sibling published skill objectstack-formula SKILL.md :407-411 already says to denormalise onto a stored field. Left as they were, after reading: the "Computed column" bullet :91-94 (still true, names no escalation target) and the Analytics Cubes section :433-468 (its defineCube example uses identifiers and '
    '; Rule 1 concerns the cube-level sql; nothing there is false). No Cube rule was added, because the parse refusal carries the prescription.",
    "docs/qa/platform-checklist/areas/dashboards.json, item dashboards.cube-query: revision 2 to 3. The fixture line lists the measures count / total_estimate_hours / avg_estimate_hours (showcase.cube.ts :22-44; the cube comment :38-43 says done_rate moved). The meta clause now reads "exactly its measures (namespaced showcase_delivery.count / .total_estimate_hours / .avg_estimate_hours)", with no count written. The verify text says "names each measure and dimension the clause lists" instead of "all four measures". The variant "measure done_rate (type: number, computed CASE expression)" is removed, and a history entry (revision 3, 2026-10-01, ref #20943) says what moved and that the steps, the reconciliation clauses and the four dimensions are unchanged. This follows README.md Lifecycle "Change" (bump revision, append history), and the validator's revision-equals-last-history check passes."
    ],
    "adr0021_read": "Read whole (446 lines before the note; line numbers below are at base 6073bb9). Nothing else is made false by this retirement. :16 Correction: dated, and about joins and RLS. :118 the "zero raw expressions" principle is now enforced one layer lower. :123 "expressible four ways (Cube DSL, ...)" is dated decision-time context. :140-143 (the D1 sketch comment, "Cube Dimension/Metric ABSORBED") and :379 (D-A: "the author-facing Cube DSL is retired/absorbed"; "smaller than both Cube-with-raw-SQL and QuerySchema") are decision-time rationale. They were already at odds with an authorable analytics_cube before ruling D, so the drift is pre-existing (below, not filed). :358-362 Finding 2 (dated scan 2026-05-31) describes the Cube layer's joins and RLS and says nothing about member expressions. :442 open follow-ups: unrelated.",
    "tests": "All on head 68593d4 (git rev-parse --short HEAD after the final commit; no later commit). dispatch-gates --commands --repo objectstack-ai/objectstack, no paths given: exit 0, 31 commands for 3 paths (23 by path, 8 whole-tree). All 31 were run with exit codes recorded before any pipe. --ran reconciliation: "31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN", exit 0. 29 exited 0, including check:adr-links, check:adr-symbol-anchors ("2166 anchors across 140 records resolve"), check:adr-anchors, check:doc-authoring, check:nul-bytes, check:skills-token-ratchet (dashboards.md 6211 tokens, ceiling 6252), check:skill-identifier-liveness, check:skill-frame-sync, check:skill-compatibility, check:pm-governed-merges, check:closing-keyword-parity, check:cross-package-test-inputs and check:comment-mask-corpus. check:doc-formula-expressions first answered exit 3 (PREREQUISITE NOT MET, formula and lint unbuilt). It answered exit 0 after "turbo run build --filter=@objectstack/formula --filter=@objectstack/lint" under os-verify-lock (VERDICT command-exit 0, 4/4 tasks). pnpm check:platform-checklist exit 1 with 1 problem, the areas/identity-auth.json anchor packages/plugins/plugin-auth/src/auth-plugin.ts#twoFactor (ABSENT SYMBOL). The same single problem gives exit 1 on the untouched base 6073bb9 and on a detached origin/main 5dbeb7d worktree (clean status, removed after). It is main's, not fixed here, and the dashboards.json edit adds no problem. node scripts/pm/check-skill-line-ratchet.mjs exit 0 and check-skill-id-lint.mjs exit 0 (34 files clean); neither scans skills/** by its own header. Skill readings: dashboards.md 468 lines / 24842 bytes / 6211 tokens before, 468 / 24843 / 6211 after. Sum of every skills//SKILL.md: 4395 lines before and after (none touched). Lint: eslint --no-inline-config --format json over the 3 changed files gave 3 results, each 0 errors and 1 warning "File ignored because no matching configuration was supplied". The eslint.config.mjs population is /*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} (:971) and the diff has no file in it. skip-changeset criterion measured: 69 non-private package.json files[], 0 cover the three paths. Positive control: the same matcher hits service-analytics README.md and spec json-schema. skills/ reach customers through npx skills add from GitHub, not npm. Not run: check:skill-examples, which the derivation did not name; no fenced code block was touched. No ablation: prose and JSON only, with no gate or test added.",
    "ci": "Head 68593d4, check-runs deduped by name (latest started_at), read once at report time: 29 distinct, 13 success, 7 skipped, 9 in_progress. In progress: Check Changeset; Lint & Repo Gates; Test Core 1/6, 2/6, 3/6; Type Check consumer gates, debt ledger, source gates, workspace. Successes include Governed Surface Queue Guard, Dogfood Regression Gate, Part-of PR must not also close its card, and Test Core 4/6, 5/6, 6/6. Not awaited. Check Changeset is expected to go red after its label window: there is no changeset and no skip-changeset label, and the dispatch limited my writes to three.",
    "governed": "check-governed-merges --pr 21071 (paths read off the PR's file list): exit 3 GOVERNED, landing tier H. docs/adr/ has 1 path and skills/
    has 1. docs/qa/platform-checklist/areas/dashboards.json is not on the register. 29 changed lines, under the 5000-line threshold. --branch claude/issue-20943-adr0021-note gives the same verdict. PR read-back: draft true, assignee os-justin, labels documentation and size/s (both from the labeler), 0 requested reviewers, 3 files, and the body is byte-identical to what was sent (930 bytes).",
    "seat_actions": [
    "Apply skip-changeset on #21071 (criterion measured above), or Check Changeset stays red.",
    "The PR body has the dashboards.md file reading but not the package reading (sum of skills SKILL.md: 4395 lines before and after), and it has no "## 维护者速读(草稿)" section. My contract asks for both on a governed PR; the dispatch said to keep the body very short because it is the seat's. I did not PATCH. Draft for the seat to insert: 改了什么:三个文档文件。ADR-0021 在 D1 末尾加一条带日期的注记,记录裁决 D(分析 cube 的度量与维度 sql 只能是列引用,表达式在解析时被拒);对外发布的看板技能不再教「超出 dataset 就去手写带原始 SQL 的 Cube」,也不再把公式字段当出口;QA 清单的 cube 条目去掉已迁到 dataset 的 done_rate。为什么改:代码半边 #20998 已落地,这三处文字随之失真;技能是 AI 写元数据的语料。风险与代价(含回滚):纯文字、无代码、无发版;技能文件行数与 token 数不变;回滚即 revert。席位意见:(留空)。你要做的:审阅 ADR 注记与技能那一段措辞,同意即批准(Tier H)。"
    ],
    "mcp_calls": "0",
    "api_writes": "3 relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #21071, run 36815673169, read-back 930 bytes identical); (2) label-write --assign os-justin → POST /repos//issues/21071/assignees (run 36815717811, read-back matches, zero label writes); (3) this os-dev-report → POST /repos//issues/20943/comments. git push is not counted: one empty-branch probe push and one commit push. Reads were REST GETs: the card and its comments, 31 open PRs' file lists (none touches the three files), check-runs and rate_limit.",
    "open_questions": [
    {
    "question": "Conflict, not resolved silently: the dev contract says a governed-surface PR body carries a "## 维护者速读(草稿)" section and, for skills/**, both size readings. The dispatch said the rest of the body is very short and belongs to the seat. Which one governs this PR?",
    "options": [
    "A: the seat inserts the drafted section and the package reading (text in seat_actions)",
    "B: leave the body as opened"
    ],
    "recommendation": "A, because the contract makes both due on every governed PR, and the seat owns the body edit."
    }
    ],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted, not filed: ADR-0021 D-A (the Resolved decisions table) says "the author-facing Cube DSL is retired/absorbed", and the D1 sketch comment says the Cube Dimension/Metric are absorbed into the dataset. On main analytics_cube is still authored (defineCube, defineStack analyticsCubes, examples/app-showcase showcase.cube.ts), and ruling D treats it as a live authored surface. This is decision-time text that predates ruling D and is not made false by it; it is a read-only inference, and no door was measured."
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling on report 5924841117 · 2026-10-01

    domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1), holder of claim 5921298734.

    • Verified against GitHub: PR docs: a cube member's sql is a column reference, in ADR-0021, the dashboards skill and the cube checklist item #21071 is at head 68593d46d8, the head the report names.
      • It has 3 files (+18 / -11), is a draft, is assigned to os-justin, and says Part of #20943.
      • check-governed-merges --pr 21071: GOVERNED, landing tier H (docs/adr/**, skills/**). The maintainer merges.
    • Delivered as dispatched:
      • ADR-0021 has a dated note after D1, naming MetricSchema / DimensionSchema sql.
      • The dashboards skill no longer escalates to a raw-SQL cube.
      • Checklist item dashboards.cube-query is at revision 3, without done_rate.
    • Beyond the retirement, declared by the dev: the skill sentence also drops "formula field" as an escalation target. The dev gives the code lines it rests on: a formula field stores no column, and an aggregate over one is refused. Accepted pending the at-tier review, which judges it.
    • Q1: A. The seat inserts the ## 维护者速读(草稿) section and the skills-package size reading into the PR body in this act. 席位意见 stays blank until ACCEPT.
    • No changeset: skip-changeset is added to the PR in this act. The dev measured that no package's files[] covers the three paths.
    • check:platform-checklist exit 1: it fails on the identity-auth.json twoFactor anchor. The dev reproduced it on a detached origin/main, so it is not this PR's.
    • Out of scope: ADR-0021's D-A row and its D1 sketch say the Cube DSL is absorbed, while analytics_cube is still authored. That text predates ruling D. Noted, not filed.
    • Next:
      • needs:contract-review goes on the PR, and it is subscribed to this session.
      • Then the CI wait and the at-tier review.
      • At ACCEPT, the Tier H hand-off: the 速读 comment, needs-user-decision, and review requests. The PR stays draft.

    Generated by Claude Code

  8. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20943,
    "status": "done",
    "branch": "claude/issue-20943-adr0021-note",
    "pr": "#21071",
    "session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 (mode:subagent, the dispatching session's id)",
    "premise_still_valid": true,
    "summary": "Cut round for review FAIL 5925160445 (finding E). New head 5ed7f87, one commit appended to 68593d4 with no merge or rebase. The diff stat over 68593d4 is 2 files, +3 / -3, and the PR is now 5 files, +21 / -14. Eval 2 now escalates to a stored or materialized rolling field or app code, not a Cube, and its must_contain drops "Cube". The evals README names a stored field or app code. Both files shrink.",
    "sentences": [
    "skills/objectstack-ui/evals/analytics-inline-vs-dataset.json :18, eval 2 expected_output. Before: "Escalates to a hand-authored Cube (raw SQL) or a stored/materialized rolling field". After: "Escalates to a stored/materialized rolling field or app code, not a Cube" (82 to 72 characters). File: 4406 bytes / 1102 tokens before, 4388 / 1097 after (ceiling 1102). must_contain changed from ["window", "Cube"] to ["window"]. Reason: with Cube now named only as a non-target, a correct answer need not name a Cube at all, so requiring "Cube" would fail correct answers; it also cannot tell "not a Cube" from "a Cube". "Cube" was not added to must_not_contain, because that would flag a correct "not a Cube". No positive escalation term was added, because must_contain is AND and an answer may name either target. must_not_contain ["dateGranularity"] is unchanged.",
    "skills/objectstack-ui/evals/README.md :10. Before: "must escalate to a Cube or a stored rollup field". After: "must escalate to a stored field or app code". File: 1148 bytes / 287 tokens before, 1143 / 286 after (ceiling 289). I wrote "a stored field", not the dispatch's "a stored rollup field", for two reasons. "a stored rollup field or app code" is 2 bytes longer than the old text, against "no longer than the current text". And the iron rule at dashboards.md :78 says "a stored field on the object (a summary rollup, or a field holding the computed value or bucket) or app code", which covers both eval 2 (rolling field) and eval 3 (rollup)."
    ],
    "tests": "Head 5ed7f87. dispatch-gates --commands --repo objectstack-ai/objectstack over the two paths: exit 0, 23 commands. The tool warned STALE TREE because origin/main 2821e9f changed scripts/sdui-manifest.record.json. The same two-path derivation from a detached origin/main 2821e9f worktree (removed after) printed an identical list of 23, so the warning does not move it. No PR path changed on main since base 6073bb9, so no merge was needed. All 23 ran (formula and lint built first under os-verify-lock, VERDICT command-exit 0), and every one exited 0. --ran: "23 derived, 23 run, 0 NOT-MEASURED, 0 UNRUN", exit 0. check-skills-token-ratchet exit 0: README 286 of 289, analytics-inline-vs-dataset.json 1097 of 1102, dashboards.md 6211 of 6252. Eval-fixture validator: none in the repo; git grep for must_contain outside skills/ hits only a CHANGELOG, and check-skills-token-ratchet is the one script that reads evals. The whole-branch derivation is 31 commands; its 8 extra (adr links and symbol anchors, adr-anchors, future-spec-major, platform-checklist, pm-prior-rulings) cover round-1 paths this commit does not touch and were run green in round 1, apart from main's platform-checklist twoFactor red. check-governed-merges --pr 21071: exit 3 GOVERNED, tier H, 5 paths (docs/adr x1, skills x3, plus dashboards.json off the register), 35 changed lines.",
    "grep": "Three git grep runs over skills/ at the head. (1) The word cube on a line that also says escalat, raw sql, custom sql, hand-authored or expression: 2 hits. evals/analytics-inline-vs-dataset.json :18 is the new "not a Cube". rules/dashboards.md :466 is Cube Rule 1, "do not put raw SQL there", a prohibition. (2) escalat across skills/: in objectstack-ui, rules/dashboards.md :62 / :66 / :78 (the dataset envelope, now a stored field or app code), :373 (the drawer "Open in list" escape hatch, not analytics), evals/README.md :10 (new) and evals json :3 / :18. navigation.md :68 is unrelated, and the rest are support-case "escalated" domain examples in other skills. (3) raw sql, custom sql or CASE WHEN: dashboards.md :76 (a dataset has no raw SQL), dashboards.md :466 (Rule 1) and objectstack-data rules/indexing.md :158 (a partial index issued as raw SQL from a runtime migration, unrelated). Result: no sentence under skills/ still names a Cube or raw SQL as an escalation target, so no out-of-file hit is left to report.",
    "ci": "Head 5ed7f87, check-runs read once, deduped by name to the latest started_at: 30 distinct, 20 success, 8 skipped, 2 in_progress (Lint & Repo Gates, Test Core 1/6). Not awaited. PR read-back: draft true, labels documentation, size/s, skip-changeset and needs:contract-review. The body and labels were not touched.",
    "mcp_calls": "0",
    "api_writes": "1 relay stroke, this comment: POST /repos/objectstack-ai/objectstack/dispatches executed as POST /repos//issues/20943/comments. git push is not counted (one push, 68593d4 to 5ed7f87). Reads were REST GETs: review comment 5925160445, check-runs and the PR object.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21071 → d19da5d620 (the governed half); the card is done

    domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1) · 2026-10-01. Holder of claim 5921298734.


    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

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:specpriority:p1High: required for production / M2security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions