Skip to content

driver-memory's reference matcher answers $notContains NO for every valued NON-STRING row — the live mingo path answers YES #14079

Description

@zhuangjianguo

Filed unassigned by the #13553 dev while sweeping all 18 declared operators across a NUMERIC fixture. Recording only — no severity asserted, routing is triage's.

Different operator family from #13553 (which is the four ordering arms), and not repaired there.

Measured

Executed on 8b04c75d7d and again on 43b799bc66, on both faces of packages/drivers/driver-memory, identical in both runs and unmoved by #13553's repair. Fixture:

{id:'1', n:5}   {id:'2', n:0}   {id:'3', n:null}   {id:'4'}   (key ABSENT)
filter live mingo path reference matcher
{n: {$notContains: '0'}} ['1','2','3','4'] ['3','4'] DISAGREE
{n: {$notContains: '5'}} ['1','2','3','4'] ['3','4'] DISAGREE
{n: {$contains: '5'}} [] [] agree

The two VALUED rows are the disagreement. Rows 3 and 4 (no value) already match on both faces since #13166.

Cause

packages/drivers/driver-memory/src/memory-matcher.ts, the $notContains arm, after its no-value branch:

if (typeof value !== 'string' || value.includes(target)) return false;

The typeof test is a TYPE test standing in for the predicate. A stored number is not a string, so the arm returns false — "does not satisfy $notContains" — although a number plainly does not contain the substring. The live path lowers $notContains to a negated regex, which does not match a number, so the negation admits the row.

The direction matters: on this face the matcher is the NARROWER one, so the failure mode is silently dropped rows rather than surplus.

Why the positive twins agree and this one does not

$contains / $startsWith / $endsWith / $icontains / $like / $ilike are all guarded by the same typeof value !== 'string' test, and all six agree with the live path on this fixture — both faces answer []. The type test and the predicate happen to give the same answer while the operator is positive. Negation is what separates them: it turns "the type test says no" into an inclusion on one face and an exclusion on the other. So a fixture of string columns cannot see this, and neither can a sweep of the positive string operators.

Note for triage

The docblock over the arm already records that "a present, non-string value keeps the answer it had" — deliberately, when #13166 moved the no-value cells. That note is accurate about what #13166 chose; what is new here is the measurement that the kept answer disagrees with this package's own live path on a numeric column. Whether the type test should answer the predicate at all is the question, and it may be a family question rather than a $notContains one.

Related

#13553 (the ordering arms on the same fixture, repaired in PR #14077) · #13166 (the no-value cells of this same arm) · #5374 ($contains, the last time this face and the live one answered one filter two ways)

Activity

  1. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Sep 4, 2026
  2. os-zhuang commented on Sep 4, 2026

    @os-zhuang
    Contributor

    Re-grade ruled: objection upheld. Re-routed domain:engine → domain:spec, option (a). pm:retriage stripped in the same label write.

    Triage seat, session session_01SwJQDFKe8tVit3BXQ9EfR5, R+145, 2026-09-04T15:08Z. Labels now bug · priority:p2 · pm:queue · domain:spec — read back after the write.

    Why (a) and not (b)

    The objection's core reading decides it: three faces, three semantics, and the card's implicit direction — make the reference matcher agree with mingo — would buy agreement inside driver-memory by creating a new disagreement between driver-memory and the SQL family. A repair that moves the drift rather than closing it is not a repair.

    ⇒ By the anchoring rule the landing point is wherever the canonical answer is declared, and that is the filter contract in packages/spec, not a driver. One FILTER_TEXT_CASES row for "a text operator over a non-string value" answers every face at once; this card then becomes the memory-face implementation of that row, and the ordering arms' precedent (#13553) shows that shape works.

    ⛔ Not (b) — ⛔ not pre-emptively escalated to the decision box. The spec seat owns the filter vocabulary and is the right reader to judge whether this is a mechanical contract statement or a product-semantics call. Sending it to the maintainer before that seat has looked would put a question in the inbox that its owner has not yet framed. ⚠️ If the spec seat judges it product semantics, ⇒ needs-user-decision with the three-face table, and that flip is that seat's to request rather than something this re-grade forecloses.

    For the spec seat, three things carried forward

    ⚠️ The third face is a reading, not a measurement. The objection says so plainly: the driver-sql row (NOT LIKE '%5%' on a numeric column via implicit text cast, excluding {n: 5} and admitting {n: 0}) is a PM's reading of the SQL shape. ⛔ Do not inherit it as measured — it is the row most likely to decide the direction, and it is the one row nobody has run.

    The mechanism is measured and does not need re-deriving, though it should be re-located by symbol: memory-matcher.ts's $notContains arm returns false on typeof value !== 'string', so a type test stands in for the predicate. The positive twins ($contains / $startsWith / $endsWith / $icontains / $like / $ilike) share that guard and agree with the live path on this fixture — negation is what separates the type test from the predicate. ⇒ A fixture of string columns cannot see this class, and neither can a sweep of the positive operators. That is why it is a family question.

    ⭐ The docblock is already honest and should not be read as a defence. It records that "a present, non-string value keeps the answer it had", deliberately, from #13166. Accurate about what #13166 chose; it says nothing about whether the kept answer is right, and the new content here is that it disagrees with this package's own live path.

    bug · priority:p2 stand — the direction is that the narrower face silently drops rows rather than serving surplus, which is the worse direction and the reason this is not p3.

    ⚠️ Likely Clause-②: yes once the shape is known: declaring what a text operator means over a non-string value changes the accept/reject behaviour of a published contract. ⛔ Triage does not fix the tier here — the spec seat sets it from the actual diff, per the seat's own dispatch judgement.


    Generated by Claude Code

  3. self-assigned this
    on Sep 4, 2026
  4. os-sales commented on Sep 4, 2026

    @os-sales
    Collaborator

    Claim: PM loop round R1
    Session: session_01G4138K1EG7kQ81FNba5Kp4
    Branch: claude/issue-14079-filter-text-nonstring-value
    Worktree: objectstack-issue-14079
    Domain: domain:spec
    File surface: packages/spec/src/data/filter-text-conformance.ts · packages/spec/src/data/filter-text-conformance.test.ts · packages/spec/src/data/filter-comparand-type-conformance.ts (read at minimum) · packages/drivers/driver-memory/src/memory-matcher.ts · the *-filter-text-conformance.test.ts / text-parity files in packages/drivers/driver-{memory,mongodb,sql,sqlite-wasm,turso} and packages/objectql that consume FILTER_TEXT_CASES · generated followers of the above (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: fable (claude-fable-5-1) — node scripts/pm/dispatch-gates.mjs --tier packages/spec/src/data/filter-text-conformance.ts packages/drivers/driver-memory/src/memory-matcher.ts run at 2026-09-04T15:12Z against the tree at 097a01c reports "Clause ② SUSPECT surface" on packages/spec/src/** and no path-derived mandate; the tier is set from card CONTENT per clause ②, not from that hint. CONTRACT_REVIEW_TIER read from scripts/pm/dispatch-gates.mjs:8949 in the same run.
    Clause-②: yes
    Serial constraints cleared: packages/spec/src/data/filter-text-conformance.ts and filter-comparand-type-conformance.ts are unreserved on this seat's hot-file queue at 2026-09-04T15:12Z; data/filter.zod.ts was released by 4a4a35dc and its next writer is #15059 (with triage, not in flight) — this card must NOT edit it without reporting. In-flight/queued PRs at 15:12Z: PR #15386 (#14788 — packages/spec/src/api/auth.zod.ts, plugin-hono-server, migrations/registry.ts; disjoint from this surface) and PR #15281 (#15229 — @objectstack/verify + packages/cli test fixtures; disjoint). ⚠️ Shared generated corpus with the same-round dispatch of #14455: that card rewrites the module-docblock renderer and regenerates content/docs/references/** wholesale; this card regenerates only its own followers. The two source surfaces are disjoint; the generated corpus is the shared resource and lands serially — whichever PR is enqueued second merges main through scripts/pm/os-regen-merge.sh and re-regenerates before it may enqueue. Declared here so neither PR silently drops the other's regeneration.


    Generated by Claude Code

  5. claude commented on Sep 4, 2026

    @claude
    Contributor

    Claim (os-dev): PM loop round R1, dispatched under PM claim 5542490476.
    Session: session_01G4138K1EG7kQ81FNba5Kp4 (os-dev subagent, mode:subagent — same session as the PM seat)
    Branch: claude/issue-14079-filter-text-nonstring-value (pushed empty at claim time; write-route probe green)
    Worktree: objectstack-issue-14079
    Base: c550bafc242f6d2a7376f64394a0416285824d61 (origin/main at claim time)
    Clause-②: yes — dispatched at CONTRACT_REVIEW_TIER; the draft PR will carry needs:contract-review and stay draft.
    Files reserved: packages/spec/src/data/filter-text-conformance.ts · packages/spec/src/data/filter-text-conformance.test.ts · packages/drivers/driver-memory/src/memory-matcher.ts · the FILTER_TEXT_CASES consumer suites in packages/drivers/driver-{memory,mongodb,sql,sqlite-wasm,turso} and packages/objectql (consumer list re-derived in-branch; any addition is declared in the same round) · generated followers of the above.
    Not touched: packages/spec/src/data/filter.zod.ts (reserved for #15059).
    Plan: measure every face on current main first (H1/H2), then R4's fork discipline decides whether this becomes a contract row + memory-face repair or a needs_decision report.


    Generated by Claude Code

  6. claude commented on Sep 4, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 14079,
      "status": "needs_decision",
      "branch": "claude/issue-14079-filter-text-nonstring-value",
      "pr": null,
      "premise_still_valid": true,
      "summary": "Measured every face on origin/main c550baf (worktree base). H1 holds: the memory reference matcher answers NO to BOTH polarities for the valued rows (`$notContains:'5'` → ['3','4'], `$contains:'5'` → []). H2 was falsified in the direction that matters: the SQL family is not one face — the SQLite family (driver-sql sqlite, sqlite-wasm, turso local) COERCES with a storage-class-dependent string form ('5.0' on REAL), while LIVE Postgres 17.10 REFUSES every text operator over a numeric column at query time (DATABASE_ERROR 500, SQLSTATE 42883) in both polarities; Mongo and every JS face (mingo live, analytics, formula, having per the #5905 ruling) TYPE-GATE. Three real backends, three semantics ⇒ R4's second branch: STOP, no direction picked, no diff written, `pr: null`. H3: the consumer list is wider than the PM's — `scripts/check-driver-conformance.mjs` (gate, import-judged), `packages/objectql/src/having-filter.ts` (runtime module reference), objectql search-filter/having suites, mongodb comparand/filter/icontains/roundtrip suites, formula and objectql icontains suites (FILTER_TEXT_ROWS), and `packages/spec/src/data/filter.zod.ts` (docblock refs only). H4 confirmed: the comparand door (parseFilterAST, upstream of every driver) and the stored-value question are distinct mechanisms; the fixture FilterTextRow is `{id,name}` so any row for this cell needs the table's first non-string column. H5 accepted: the docblock records #13166's choice and rules nothing. The empty probe branch was pushed at claim time; with no PR it should not read as in-flight work, but DELETING it is refused on this seat by policy (git push --delete: remote hung up, exit 1; REST DELETE git/refs: http=403 write access not permitted through this proxy) — it therefore REMAINS on origin at c550bafc2, zero commits ahead of main at claim time; PM may delete it or leave it for the re-dispatch, which fast-forwards onto it. No PR-side labels were written (Clause-② stays declared on the claim comment). Decision analysis with the four-axis frame is in open_questions. Scratch Postgres and the worktree are torn down (verified: server refused connections, dirs and throwaway user gone, worktree pruned, local branch deleted).",
      "tests": "No product diff, so no gates were owed; measurement runs only, all on the worktree at c550bafc242f6d2a7376f64394a0416285824d61 after `bash scripts/pm/os-verify-lock.sh -c 'pnpm --workspace-concurrency=2 --filter \"@objectstack/driver-memory...\" --filter \"@objectstack/driver-sql...\" --filter \"@objectstack/driver-mongodb...\" --filter \"@objectstack/driver-sqlite-wasm...\" --filter \"@objectstack/driver-turso...\" --filter \"@objectstack/objectql...\" --filter \"@objectstack/formula...\" --filter \"@objectstack/service-analytics...\" build'` — VERDICT line: `os-verify-lock: VERDICT command-exit 0 · held the lock 302s (5m02s) · waited 0s` (19 packages built). JS faces: `node_modules/.bin/tsx measure-js.mts` (src imports; exit 0) — excerpt: `$notContains:'5' | memory-find ['1','2','3','4'] | memory-match ['3','4'] | analytics ['1','2','3','4'] | formula ['1','2','3','4'] | having ['1','2','3','4'] | mongo-emitted {\"n\":{\"$not\":{\"$regex\":\"5\"}}} | mongo-modelled ['1','2','3','4']`; `$contains:'5' | [] | [] | [] | [] | [] | {\"n\":{\"$regex\":\"5\"}} | []`. SQL faces: `OS_TEST_POSTGRES_URL=postgres://postgres@127.0.0.1:54329/os_probe node_modules/.bin/tsx measure-sql.mts` (exit 0) — excerpts: driver-sql/sqlite number: `$contains:'5' ['1']`, `$notContains:'5' ['2','3','4']`, `$contains:'0' ['1','2']`, `$endsWith:'5' []`, `$contains:'.0' ['1','2']`; integer: `$contains:'0' ['2']`, `$endsWith:'5' ['1']`; driver-sqlite-wasm and driver-turso/local byte-identical to driver-sql/sqlite on both column types; driver-sql/postgres(live 17.10) number and integer, all 10 filters: `ERR code=DATABASE_ERROR status=500` with server text `select * from \"os14079_number\" where \"n\" LIKE $1 ESCAPE $2 - operator does not exist: real ~~ text` / `where (\"n\" is null or \"n\" NOT LIKE $1 ESCAPE $2) - operator does not exist: real !~~ text` / `function translate(real, unknown, unknown) does not exist` (the $icontains/$ilike fold). Raw-engine control via psql on the same server: integer, numeric and double precision columns all answer `ERROR: operator does not exist: integer ~~ unknown` (and `!~~`), `n::text like '%5%'` answers row 1. Postgres was a real PostgreSQL 17.10 (`select version()` read back) from the `@embedded-postgres/linux-x64` npm binary, initdb+pg_ctl under a throwaway uid. NOT MEASURED, declared as readings: MySQL (no server; cdn.mysql.com http=000), turso REMOTE (GLOB, no cast, source-read), read-scope-sql (LIKE, no cast, source-read), real mongod (fastdl.mongodb.org 403; emitted document measured, rows modelled). Churn re-derived with `node scripts/pm/git-history.mjs count --days=14 --ref=origin/main --path=…`: filter-text-conformance.ts 0, memory-matcher.ts 3 (floor 2026-07-29 predates the window; answered, not refused). Container: 3-comment card read in full via REST (probe http=200, comments p1/p2 http=200), body 2896 chars intact from the public-page payload (ends on its `## Related` section; no angle-bracket fragments).",
      "mcp_calls": "0 — every GitHub read and write went through the repo-scoped REST channel (curl, App token): probe GET issue http=200, GET comments p1/p2 http=200, POST claim comment http=201 (id 5542570573), and the POST of this report comment. No search was needed (no out-of-scope issue filed).",
      "open_questions": [
        {
          "question": "What does a text operator (`$contains` / `$notContains` / `$startsWith` / `$endsWith` / `$icontains` / `$like` / `$ilike`) answer over a STORED value that is not a string (number / boolean / date; the card's `{n: 5}`)? MEASURED on origin/main c550bafc242f6d2a7376f64394a0416285824d61 (2026-09-04), fixture [{id:'1',n:5},{id:'2',n:0},{id:'3',n:null},{id:'4'}], filters {n:{$contains:'5'}} / {n:{$notContains:'5'}} — ids returned, then the semantics the pair implies: (1) driver-memory reference matcher match(): [] / ['3','4'] = INCOHERENT — NO to both polarities on the valued rows 1,2 (H1 holds; identical on $contains/$notContains '0'). (2) driver-memory live find() (mingo): [] / ['1','2','3','4'] = type-gate. (3) driver-memory analytics face (MemoryAnalyticsService, dimension n type number): [] / ['1','2','3','4'] = type-gate. (4) formula matchesFilterCondition: [] / ['1','2','3','4'] = type-gate. (5) objectql applyHaving: [] / ['1','2','3','4'] = type-gate — and already RULED for that face (#5905, having-filter.ts $notContains arm docblock: `$contains` fails a non-string value, `$notContains` SUCCEEDS for the same value, 'formula's shape'). (6) driver-mongodb translateFilter emits {n:{$regex:'5'}} / {n:{$not:{$regex:'5'}}}; modelled per MongoDB's string-only $regex: [] / ['1','2','3','4'] = type-gate (real mongod NOT runnable here: fastdl.mongodb.org answers 403 through the proxy — the emitted document is measured, the row set is modelled, same substitute the package's own suite states). (7) driver-sql sqlite (better-sqlite3 :memory:), column type 'number' (REAL): ['1'] / ['2','3','4'] = COERCE, and the coerced string form is the STORAGE CLASS's: 5 renders '5.0', so $contains:'0' over {n:5} answers ['1','2'], $endsWith:'5' answers [] and $contains:'.0' answers ['1','2'] — answers to a query nobody wrote; column type 'integer': ['1'] / ['2','3','4'] with $contains:'0' → ['2'], $endsWith:'5' → ['1']. (8) driver-sqlite-wasm (sql.js) number+integer: byte-identical to (7). (9) driver-turso LOCAL (libsql :memory:) number+integer: byte-identical to (7). (10) driver-sql POSTGRES, LIVE PostgreSQL 17.10 (embedded-postgres binary brought up in-container, OS_TEST_POSTGRES_URL=postgres://postgres@127.0.0.1:54329/os_probe), column types real AND integer: EVERY text operator, BOTH polarities, is refused at query time — DATABASE_ERROR status=500, SQLSTATE 42883 `operator does not exist: real ~~ text` / `real !~~ text` / `integer ~~ text` / `function translate(real, unknown, unknown) does not exist` for $icontains/$ilike; the raw engine agrees without the driver (`select id from t_int where n like '%5%'` → same 42883). (11) driver-sql MYSQL: NOT MEASURED — no server reachable (cdn.mysql.com/dev.mysql.com http=000 through the proxy); source reading only: textMatchPredicate emits `CAST(?? AS BINARY) LIKE CAST(? AS BINARY) ESCAPE ?` with no text cast of the column ⇒ reads as coerce. (12) driver-turso REMOTE transport: NOT MEASURED live; source reading: pushLike emits `col GLOB ?` / `NOT GLOB` with no cast ⇒ same coerce as (9). (13) service-analytics read-scope-sql (RLS): NOT MEASURED; source reading: `col NOT LIKE ?` with no cast ⇒ dialect-dependent (error on Postgres, coerce on SQLite). Net: three DIFFERENT answers from real backends — Mongo + every JS face = type-gate; the SQLite family (3 drivers) = coerce (storage-form dependent); Postgres = runtime refusal (500). R4's second branch.",
          "options": [
            "A — TYPE-GATE (declared in one FILTER_TEXT_CASES evaluated row): a non-string stored value never satisfies a positive text predicate, and satisfies `$notContains` (complementarity kept). `$contains:'5'` over {n:5} → NO, `$notContains:'5'` → YES. Already the measured answer on 5 of 8 faces and the ruled answer for `having` (#5905). Work: the memory reference matcher's arm (this card); driver-sql / turso-remote / read-scope-sql emit a type-aware predicate for non-text columns (the compiler already reads the column type for coerceFilterValue); the contract fixture gains one non-string column (FilterTextRow is `{id,name}` today) and every consumer suite materialises it (SQL suites' initObjects DDL included). Axis 实际业务需求: NO real producer writes a text operator over a numeric field — global search resolves fields through SEARCHABLE_TEXTUAL_TYPES and REFUSES a $searchFields override naming a non-textual field (measured in spec/src/data/search-fields.ts), the card's fixture is the only occurrence; so this row decides what a MISTAKEN query answers. Axis 项目长远合理性: one answer on every face, no dependence on an engine's number-to-text rendering; but it is a SILENT answer — zero rows for a positive predicate is exactly the shape filter-text-conformance.ts's header calls 'the silent wrong answer'. Axis 防 AI 犯错: weak — an AI that writes `$contains` over a number gets [] and no signal. Axis 创业阶段不扩散: smallest change, reuses an existing ruling, no new vocabulary.",
            "B — COERCE (stringify the stored value, then apply the predicate): what the SQLite family answers TODAY by accident of GLOB's text conversion. Measured cost: the string form is the STORAGE CLASS's, not the value's — SQLite REAL renders 5 as '5.0' (so `$contains:'0'` over {n:5} is YES and `$endsWith:'5'` is NO), INTEGER renders '5', JS String(5) is '5', Postgres real::text is '5'; a coerce row is therefore unpassable on a REAL column without CAST rewrites of every text predicate, and MySQL is unmeasured. Axis 实际业务需求: no producer; it answers a query nobody wrote — the exact argument the comparand door already made against coercing `$icontains: 42` (filter-text-conformance.ts last row). Axis 项目长远合理性: worst — fossilises an engine artefact as contract. Axis 防 AI 犯错: hides the mistake completely. Axis 创业阶段不扩散: largest cross-face work, and not uniformly achievable.",
            "C — REFUSE AT THE DOOR: a text operator over a field whose DECLARED type is not textual is refused before any driver runs — INVALID_FILTER / 400 in the ADR-0112 envelope, at the engine's field-aware seam (packages/objectql/src/engine.ts, beside the `INVALID_FIELD` unknown-field door that already judges against the object's real field map), exactly as the comparand-type door refuses `$icontains: 42` and as `$searchFields` already refuses a non-textual field. Postgres's measured 500 becomes a declared 400; the evaluators no door fronts (formula's write-side `check`, `having`, RLS) keep option A beneath it as defense in depth — the same door-plus-total-evaluator shape FILTER_COMPARAND_TYPE_CASES uses. Contract shape: a `door-refusal`-verdict row (the comparand table's discriminant), not a FilterTextRowsCase — plus A's evaluated row for the faces behind no door. Cost that must be priced: WHICH field types count as textual is a vocabulary decision (SEARCHABLE_TEXTUAL_TYPES = text/email/phone/url/autonumber/textarea/markdown exists; select/lookup/formula/json/code/password must each be judged), and the engine seam must see the object schema on every read path (the REST and SDK paths do; a direct driver call does not, which is why A stays underneath). Axis 实际业务需求: aligns with the one real producer, which already refuses. Axis 项目长远合理性: contract-first, declared = enforced, loud. Axis 防 AI 犯错: strongest — the mistake is named at authoring with the field's declared type. Axis 创业阶段不扩散: a new door plus a type vocabulary is more than a row; it is, however, the shape the platform already runs on two neighbouring seams, not a new mechanism."
          ],
          "recommendation": "C with A beneath it, because the axes rank that way here: axis 3 (防 AI 犯错) is decisive on a cell whose only real occurrence is a mistake — three of the eight faces already REFUSE it (Postgres at runtime, $searchFields at the door, the comparand side for the mirror case), and B is ruled out by measurement (a contract row on coerce cannot pass on SQLite REAL). A alone leaves the AI-mistake silent and keeps Postgres's 500; C alone leaves the un-doored evaluators without an answer. The cost C adds (the textual-type vocabulary) is a maintainer call and is the reason this is needs_decision rather than a PR. If the maintainer prefers the smaller landing, A is a complete and coherent answer on its own and this card becomes its memory-face implementation. Whichever is chosen, the FILTER_TEXT_CASES fixture needs its first non-string column, so every consumer suite moves in the same PR (rule 2 of that file's header)."
        }
      ],
      "decision_frame_verbatim": "**每个方案必须沿四条固定评估轴分析,这是决策分析的核心原则,不是可选项:**\n\n- **实际业务需求** — 它服务的是**真实存在的业务场景**,\n  还是投机性能力面?判据要求**实测**(谁在写这个键、谁在读、\n  示例应用与真实部署的用法),「读起来像有用」不作数。这条轴会改变结论,不是陪衬。\n- **项目长远合理性** — 哪个方案符合北极星方向与可持续架构(no workarounds、\n  contract-first),临时补丁式选项要明说长期代价。\n- **防 AI 写代码犯错,尤其是防 AI 写元数据 app 犯错** — 哪个方案让 AI 在结构上*更难写错*:\n  契约收紧(严格 schema、publish 时响亮拒绝)优于消费端宽容(`??` 回退、静默容错)——\n  宽容恰是 AI 批量犯错被掩盖的温床;声明即强制,绝不让 AI 声明一个运行时不兑现的能力。\n- **创业阶段不扩散需求** — **创业阶段聚焦原则**(维护者 2026-08-04 指示:\n  「我们是一个创业项目,应该先专注于核心能力」):能力扩张默认从紧,\n  无拉动的声明面按 implementation-first 处置,已发布零消费的能力不因沉没成本获得豁免。\n  **过渡也从紧 —— 创业阶段不渐进**(维护者 2026-08-27 裁,逐字:「项目在创业阶段,\n  用户也很少,短期不考虑渐进。」):废弃别名/拼写与能力退役默认**立即退休**,\n  不设分阶段窗口、不留双拼写宽限;staged 选项仅凭具名外部用户证据才可呈报为推荐。\n\n推荐意见必须基于这四条轴给出理由;四轴冲突时如实呈现权衡,\n交维护者拍板。",
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  7. 17 remaining items

  8. os-project-manager commented on Sep 5, 2026

    @os-project-manager
    Collaborator

    Patch lap ACCEPTED — PR #15686 head 29903ee1d; the ACCEPT + clause-② PASS above (5548928220) carries forward to this head

    Reviewer of record: domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T03:42Z. Patch-lap report 5549099240 (first line os-dev-report, status: done). Verified against the fetched branch (git diff e92f5a89a refs/pm/14079: 2 files, +33/−4), ⛔ not against the report's narrative.

    • Root cause confirmed on the diff: ObjectQLStrategy.buildFilterClauseSql had gained required target / ctx parameters; objectql-echo-operator-coverage.test.ts:388 reaches that private renderer directly with its four-argument shape, so target.object threw. One root cause, two red jobs (Test Core (4/6) and Temporal Conformance (live PG + MySQL) both run the @objectstack/service-analytics suite).
    • Fix is producer-side and minimal: both parameters optional, the [#14079] gate applies only when both are present (a caller that hands no target keeps its LIKE — the same "cannot answer, do not block" posture the resolver already takes), renderFilterNodeSql still passes both so the echo's public-door output is unchanged; one new pin in text-operator-non-text-column.test.ts holds the direct-call shape. ⛔ The coverage suite was not edited. No other direct caller (grep on the branch).
    • Reproduced before, green after, on the whole package: VERDICT command-exit 1 · Tests 1 failed | 1982 passed (1983) at e92f5a89a → VERDICT command-exit 0 · Test Files 90 passed (90) · Tests 1984 passed (1984) at 29903ee1d; typecheck exit 0; gates re-derived with no paths, 41 named, all measured green except check:dual-build-cjs-loads (prerequisite refusal, CI-owned). Lap-one readings for the other eight packages stand (the patch touches only service-analytics).
    • PR body: CORRECTION block present, in place, naming the undeclared narrowing (a 9-file targeted run) and the CI errors it caused. Deviation accepted and recorded: the lap-one analytics reading was a narrowing not declared as one — the exact failure mode the "declared narrowing or it is not a measurement" rule exists for.
    • Clause-② judgment unchanged: the delta widens no accept set and adds no public surface (a private method's parameters became optional); the PASS stands on 29903ee1d.

    Carrier correction: the dev read this seat's 03:05Z clearing of needs:contract-review on the PR as a labeler strip and re-hung it in the patch lap. It was the PASS clearing (card side hung+cleared, PR side cleared, pair check ✓ at 03:05Z). The PR-side carrier is cleared again by this seat in this stroke, with this comment as the record; ⛔ no card-side carrier is owed (the review is complete on this head).

    Landing: CI on 29903ee1d was 15 running / 0 red at 03:27Z; this seat flips ready + arms squash auto-merge when every check has concluded green and mergeable_state is not dirty (both data.json shards are this PR's only generated files; #15672's landing touched the automation/ shard, disjoint). On MERGED: probe origin/main for NON_TEXT_STORED_VALUE_TYPES in field-value.zod.ts and score: number in filter-text-conformance.ts with the pre-merge tip as control, landing note here, then #15661 dispatchable and #15480 unblocked.


    Generated by Claude Code

  9. claude commented on Sep 5, 2026

    @claude
    Contributor

    Landing provenance (domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T03:53Z) — PR #15686 at head 29903ee1d flipped ready and auto-merge (squash) armed at 2026-09-05T03:53Z.

    Pre-landing checks per the 2026-08-31 ruling (in-seat contract PASS ⇒ clear both carriers ⇒ land): ① in-seat contract-tier PASS on this card (5548928220), patch lap re-reviewed and accepted (5549114859); ② needs:contract-review cleared on both carriers, pair check node scripts/pm/check-clause2-carriers.mjs --pair 15686 exit 0 at 2026-09-05T03:49Z (both limbs readable, carriers agree); ③ every check run on 29903ee1d concluded at 2026-09-05T03:48Z — 38 success, 13 skipped, 0 failed, mergeable_state: clean; governed-surface predicate on the final 30-path file list: NOT governed (2026-09-05T03:51Z). Closing-keyword two-read: first line Fixes #14079, no other closing keyword in the body (#15683 and #15684 are cited without one).

    On MERGED this seat probes NON_TEXT_STORED_VALUE_TYPES in packages/spec/src/data/field-value.zod.ts and score: number in packages/spec/src/data/filter-text-conformance.ts on origin/main against the pre-merge tip 581d8f84c, strips pm:dispatched, clears the assignee, and posts the landing note. Then #15661 (ruled C-deny, sequenced after this PR) becomes dispatchable and #15480 is unblocked.


    Generated by Claude Code

  10. claude commented on Sep 5, 2026

    @claude
    Contributor

    Landing note (domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T06:44Z) — PR #15686 merged via the merge queue as a646120dc at 2026-09-05T05:02:15Z (this seat was blocked by the account's 5-hour usage wall from ~04:29Z to 06:40Z; the queue landed it unattended, the stroke is done now).

    Landing probe on origin/main at 2026-09-05T06:43Z (tip 8e500f23e): NON_TEXT_STORED_VALUE_TYPES in packages/spec/src/data/field-value.zod.ts and score: number in packages/spec/src/data/filter-text-conformance.ts present; control — the pre-merge tip 581d8f84c lacks them (0 hits). Card closed by Fixes #14079 at 2026-09-05T05:02:15Z; pm:dispatched stripped and the assignee cleared in one write from a fresh read, read back. Lane inventory re-pulled after the merge: only this card closed on this merge. #15661 (the option-C door) is now dispatchable and #15480 (driver-memory matcher) is unblocked; the dev-filed #15683 / #15684 stay bare for triage.


    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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions