Skip to content

Commit 5725b52

Browse files
os-zhuangclaude
andauthored
#3071 诊断与修复:plugin-audit#test 在托管 runner 上静默失败 (#3073)
* feat(transport): prove the IHttpServer port is Hono-free — second adapter on raw node:http (ADR-0076 D11/OQ#10, #2462) Multi-adapter was designed but unproven: only the Hono adapter existed, and ADR-0076 D11 flagged 'the normalized context shows Hono-isms' as a caveat blocking the transport decomposition. This adds the validation: - New @objectstack/plugin-node-server: a thin IHttpServer implementation on raw node:http with ZERO dependencies beyond @objectstack/core — :param + trailing-* routing (registration-order first-match, same as the primary adapter), eager JSON/urlencoded body parse with lazy rawBody() for binary, SSE via native res.write/end, 404/405-with-Allow semantics, EADDRINUSE retry, graceful drain on close. Deliberately no getRawApp()/mount(). - Cross-adapter conformance suite: boots the dispatcher bridge AND the REST route generator (with ObjectQL + memory driver) on BOTH adapters over real sockets — /data CRUD roundtrip, /meta reads, /ready, /health, discovery, :param routing, 404/405 parity, plus a probe-for-probe response-shape parity matrix between the two adapters. 40 assertions, all green. Findings recorded in ADR-0076 (OQ#9/OQ#10/OQ#11 resolution notes): - The port has NO hard Hono-isms. The Host-header backfill in the Hono adapter is a Fetch-API artifact local to that adapter, not a port leak. - All remaining Hono coupling is confined to the feature-detected getRawApp() escape hatch (metadata HMR, cloud-connection routes, static/SPA + CORS + Server-Timing) which degrades gracefully. - Follow-up for the D11 window: codify res.write/res.end (SSE) and getPort() — extensions consumers already rely on — plus 404/405 semantics into the IHttpServer contract. - OQ#9 audit verdict (delineate, don't merge; only discovery + packages are genuinely double-mounted) recorded in the ADR open-questions list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1BtxNeUaGmvUYr8FwtUNu * fix(plugin-node-server): harden query/params against prototype pollution (CodeQL) CodeQL flagged remote property injection: query-param keys come straight from the request URL, so building the map on a plain object literal let '?__proto__=…' write through the prototype chain. Use null-prototype objects for query and params and drop the dangerous keys (__proto__/constructor/prototype) outright; regression test pins that '?__proto__=polluted' is dropped and Object.prototype stays clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1BtxNeUaGmvUYr8FwtUNu * fix(plugin-node-server): eliminate the dynamic property-write sink CodeQL flags The blacklist guard added in the previous commit was not recognized as a sanitizer by CodeQL's js/remote-property-injection rule — the sink is the computed write itself (obj[userKey] = …). Build query and params via Object.fromEntries (own data properties only, no prototype-chain walk) so the sink no longer exists; the __proto__/constructor/prototype drop stays as defense in depth. Regression test unchanged and green (41/41). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1BtxNeUaGmvUYr8FwtUNu * refactor(qa): reposition the port-conformance adapter as a private QA gate — packages/qa/http-conformance Maintainer review: the node:http adapter is a validation instrument, not a product server, so it must not ship as a publishable plugin- package. Reposition it accordingly: - Move packages/plugins/plugin-node-server → packages/qa/http-conformance; rename @objectstack/plugin-node-server → @objectstack/http-conformance, private: true, test-only (no build/publish), matching the existing verification-gate packages (dogfood, downstream-contract). - packages/qa/ becomes the unified home for non-published verification gates (new pnpm-workspace glob); migrating dogfood/downstream-contract there is proposed as a follow-up. - Drop the changeset + fixed-group entry (private packages are not versioned); update the ADR-0076 OQ#10 note to the new name. Behavior unchanged: same adapter, same 41-assertion cross-adapter conformance suite, all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1BtxNeUaGmvUYr8FwtUNu * refactor(qa): migrate dogfood + downstream-contract into packages/qa/ (unified verification-gate home) Completes the packages/qa/ consolidation started with http-conformance: all three non-published verification gates (dogfood regression gate, downstream-contract compatibility gate, http-conformance port gate) now live under one directory. Mechanical path migration — no behavior change: - git mv packages/{dogfood,downstream-contract} → packages/qa/ - rewrite every 'packages/dogfood' / 'packages/downstream-contract' breadcrumb: spec liveness proof registry + JSON ledgers, spec-liveness-check.yml path trigger, eslint ignore, docs/ADRs, source comments - bump the repo-root traversal one level in the four dogfood conformance tests that resolve REPO_ROOT from __dirname Verified from the new locations: dogfood 273 passed / 3 skipped, downstream-contract typecheck + 14 tests, spec check:liveness resolves all bound proofs, http-conformance 41/41. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1BtxNeUaGmvUYr8FwtUNu * chore: retrigger CI — Actions did not fire on dc6507c Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1BtxNeUaGmvUYr8FwtUNu * docs(changeset): fix dogfood path breadcrumb after packages/qa migration Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1BtxNeUaGmvUYr8FwtUNu * fix(ci): build the examples' dependency closure explicitly in the typecheck job + correct stale ADR-0076 D12 status Two follow-ups after merging latest main: - lint.yml: the example-app typecheck step built only ./packages/* (direct children). The connector packages the showcase imports were built purely by accident — through dogfood's dependency chain — which broke when dogfood moved to packages/qa/. Request the examples' dependency closure explicitly (--filter='./examples/*^...') so the step no longer depends on which package happens to live at the top level. - ADR-0076: the round-2 status verification merged from main (#3061) was read against a pre-#3028 snapshot — svcAvailable no longer hardcodes 'available'; the D12 framework side shipped in #3028. Correct the status line and note OQ#9/OQ#10 resolution (#3037). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1BtxNeUaGmvUYr8FwtUNu * fix(ci): cap turbo test concurrency at the runner's core count (OOM mitigation) Test Core started failing deterministically on the hosted runner — plugin-audit#test dies with ZERO vitest output (kernel OOM-kill signature) while passing locally on Node 20 and 22 — first on main @4f8c2d1, then twice on this PR (initial run + job re-run). Onset correlates with the task graph growing past ~100 tasks (connector packages + qa gates): turbo's default concurrency (10) schedules that many vitest workers + tsup DTS builds onto a 4-vCPU runner and peak memory tips over. Cap test-step concurrency at 4 (PR affected-tests + push full-run). CPU-bound workload, so wall-clock cost is minimal; peak memory becomes bounded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1BtxNeUaGmvUYr8FwtUNu * fix(qa): bump repo-relative path in the newly-merged declarative-mcp dogfood test + regen lockfile after merge showcase-declarative-mcp.dogfood.test.ts landed on main (#3062) against the old packages/dogfood location; after the packages/qa migration its '../../../examples/app-showcase' URL resolved inside packages/. One more level, same as the other conformance tests. Lockfile regenerated after taking main's side in the merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1BtxNeUaGmvUYr8FwtUNu * ci: TEMPORARY #3071 diagnostics — isolated verbose plugin-audit runs + dmesg OOM probe plugin-audit#test dies on the hosted runner with zero captured output (3x deterministic since main@4f8c2d1) while passing locally under Node 20/22 with the exact CI command. Bracket the turbo test step with an isolated, verbose, ungrouped vitest run (before: fresh machine; after: post-graph machine + kernel OOM traces) so the real failure output cannot be lost to the GH log pipeline. SIGQUIT-on-timeout dumps Node thread stacks if it hangs. To be REMOVED once #3071's root cause is fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1BtxNeUaGmvUYr8FwtUNu --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent aeb2110 commit 5725b52

1 file changed

Lines changed: 29 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,41 @@ jobs:
115115
# while passing everywhere else — first seen on main @4f8c2d1,
116116
# reproduced twice on #3037. Matching concurrency to the core count
117117
# bounds peak memory; the job is CPU-bound anyway.
118+
# ── TEMPORARY DIAGNOSTICS for #3071 — remove before merge ────────────
119+
# plugin-audit#test dies on the hosted runner with zero captured output
120+
# (3× deterministic; passes locally under Node 20/22 with the exact CI
121+
# command). Run it ISOLATED first — fresh machine, no turbo grouping,
122+
# verbose reporter — so its real output cannot be mangled by the GH log
123+
# pipeline under burst. SIGQUIT before the timeout makes Node dump all
124+
# thread stacks if it hangs.
125+
- name: DIAG 3071 — plugin-audit isolated, verbose (temporary)
126+
if: github.event_name == 'pull_request'
127+
run: |
128+
node --version
129+
free -h || true
130+
echo "── isolated vitest run ──"
131+
timeout -s QUIT 240 pnpm --filter @objectstack/plugin-audit exec vitest run --reporter=verbose --no-file-parallelism; echo "exit=$?"
132+
continue-on-error: true
133+
118134
- name: Run affected tests (PR)
119135
if: github.event_name == 'pull_request'
120136
env:
121137
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }}
122138
run: pnpm turbo run test --affected --concurrency=4
123139

140+
# ── TEMPORARY DIAGNOSTICS for #3071 — remove before merge ────────────
141+
# If the turbo step above still kills plugin-audit silently, dmesg shows
142+
# whether the kernel OOM-killer fired, and the second isolated re-run
143+
# distinguishes "machine state after the full graph" from "fresh".
144+
- name: DIAG 3071 — post-turbo dmesg + re-run (temporary)
145+
if: github.event_name == 'pull_request' && always()
146+
run: |
147+
echo "── kernel OOM traces ──"
148+
sudo dmesg | grep -iE "oom|killed process|out of memory" | tail -20 || echo "(no OOM traces)"
149+
echo "── plugin-audit re-run on post-graph machine ──"
150+
timeout -s QUIT 240 pnpm --filter @objectstack/plugin-audit exec vitest run --reporter=verbose --no-file-parallelism; echo "exit=$?"
151+
continue-on-error: true
152+
124153
# Push to main: full run, but exclude spec's plain test task — the
125154
# coverage step below executes the exact same 250-file spec suite once,
126155
# with coverage. Previously the spec suite ran twice per push.

0 commit comments

Comments
 (0)