Repository navigation
finding: dev metadata reload does not re-ingest connector items — reconcileDeclaredConnectors reads a registry the artifact reload never refreshes for connectors #7742
Description
Activity
Findings triage (grade): promoted —
findingremoved,pm:queuestays (resolves the filed dual pm-state).Why queue: the mechanism is confirmed on
origin/main@8f1851e—reconcileDeclaredConnectors(packages/services/service-automation/src/plugin.ts:1059) readsql?.registry?.listItems?.('connector')(~:1071), and the artifact reload re-ingests object definitions only, so a connector-only edit reconciles against a stale registry (a no-op). The dev loop masks it because the serve child restarts, but the named production trigger (a Studio package publish) walks straight into it. A confirmed no-op reconcile with a named landing is a defect, not an observation.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
huangyiirene commented
on Aug 11, 2026 CollaboratorAuthorMore actionsClaim: PM loop round 2
Session:session_01JVfWSQN9RDDGwdVSmMc29x
Branch:claude/issue-7742-connector-reload-reconcile
Worktree: cloud session's own clone (mode:cloud)
Domain:domain:services
File surface:packages/services/service-automation/src/plugin.ts(reconcileDeclaredConnectors~:1059, registry read ~:1071) and the reload-event wiring it needs.⚠️ If the true fix lands in the artifact-reload path insidepackages/metadata*(making the reload re-ingest connector items), that isdomain:metadataterritory — STOP-and-report fork in the dispatch, per the cross-seat protocol.
Container & model: M (reload/reconcile wiring with a cross-lane fork),mode:cloud,model: opus
Serial constraints cleared:⚠️ same-package sibling #7747 (notify acted accounting, root cause not yet located) is deferred and serialized behind this card — not dispatched this round. Same-day churn on service-automation (#7699, #7595, #7490 — all merged) touches builtin/ and run surfaces, notreconcileDeclaredConnectors; triage confirmed the mechanism on8f1851eat 13:11Z. No in-flight PR touches service-automation.
Generated by Claude Code
huangyiirene commented
on Aug 11, 2026 CollaboratorAuthorMore actions{ "issue": 7742, "status": "in_progress", "premise_still_valid": true, "premise_evidence": "Measured on origin/main @ b54aaab, not read off the issue. New test drives the REAL reload shape (a real SchemaRegistry that is never mutated across the reload + MetadataPlugin's actual `{ changed, metadata }` payload) and the reconcile is a total no-op: edited connector -> `closed` [] (expected ['billing']); deleted connector -> `closed` [] (expected ['shipping']); added connector -> not registered. Mechanism confirmed exactly as filed: `reconcileDeclaredConnectors` read `ql.registry.listItems('connector')` (plugin.ts ~:1059/:1071); ObjectQL's own `metadata:reloaded` handler re-ingests the payload's OBJECT definitions (`ingestReloadedObjects`) and nothing re-ingests connector items, so the reconcile compared the boot snapshot against itself. Also measured: every pre-existing reload test (connector-materialization.test.ts) hand-mutates a fake registry (`state.declared = next`) before firing the hook, which is why the path looked covered.", "branch": "claude/issue-7742-connector-reload-reconcile", "pr": 7847, "pr_url": "https://github.com/objectstack-ai/objectstack/pull/7847", "pr_state": "draft", "cross_lane_fork": { "triggered": false, "detail": "No metadata-side change was needed and none was made. `packages/metadata*` is untouched. The services-side shape works: the plugin re-reads its OWN input from the two sources a reload refreshes, leaving global reload semantics alone." }, "change_summary": { "files": [ "packages/services/service-automation/src/plugin.ts (+220/-30)", "packages/services/service-automation/src/connector-reload-reingest.test.ts (new, 8 tests)", ".changeset/connector-reload-reingest.md (patch)" ], "design": "New `readDeclaredConnectorItems(ctx, { post })` is the single declaration source for BOTH the reconcile and the descriptor audit. It folds two reload-refreshed sources over the existing registry read, one per trigger: (1) the artifact on the `metadata:reloaded` payload — the dev/HMR trigger, and the only place an edited or deleted definition exists — kept as plugin state so a degraded-retry firing later does not fall back to the boot snapshot and rebuild the pre-edit instance; (2) `protocol.getMetaItems({type:'connector'})` — the flattened /meta view the flow re-sync already reads, which layers the `sys_metadata` rows a Studio publish promotes to active over the registry (verified in protocol.ts `mergePackageAwareOverlay`: the overlay wins over the registry entry it shadows). The payload fold is a REPLACEMENT SCOPED to the packages the artifact speaks for (manifest id + `_packageId` on its items), not a union: a union can never observe a deletion, an unscoped replacement would tear down another package's connector.", "boot_path_unchanged": "The protocol read is `post`-only (every reconcile that is not the fatal boot one). At boot the registry was just built and is current by construction; a sys_metadata query that can fail does not belong on the fail-loudly boot path.", "fail_safe_rules": "Absent/failing protocol read -> no answer, nothing torn down (same contract as the flow re-sync's `null`). Empty served view while the registry HAS entries -> no answer (the view is a superset of the registry by construction). Payload with no `metadata.connectors` ARRAY (a publish's bare `{changed}`, or an artifact with no `connectors:` key) -> previous snapshot untouched; only an artifact carrying an EMPTY array is the honest 'none left'. Unchanged entries still hash to the same signature, so reloads do not churn live connections.", "refactor": "`readFlowDefsFromProtocol`'s body is now the shared `readMetaItemsFromProtocol(ctx, type)`; the flow warn message is byte-identical (`[Automation] flow read from protocol failed: getMetaItems('flow')`), so its pin in thrown-cause-diagnostics.test.ts still holds." }, "tests": { "new_file": "packages/services/service-automation/src/connector-reload-reingest.test.ts", "shape": "Real `SchemaRegistry` (from @objectstack/objectql), deliberately NEVER mutated across the reload — the registry going stale is the fact under test. Assertions are on the reconcile's OBSERVABLE effect (the old instance's `close()`, a re-materialization carrying the new `providerConfig`, the live connector list), never on internal call counts.", "cases": [ "edited connector on a dev artifact reload -> torn down + re-materialized from the EDITED definition", "unedited connector -> left alone (no reconnect churn)", "connector deleted from the reloaded artifact -> torn down", "connector added by the reloaded artifact -> materialized", "another package's connector -> survives an unrelated app's reload", "Studio publish (no artifact on the payload) -> re-materializes off the published /meta view while the registry stays stale", "empty or throwing published view -> never tears down live connectors", "payload with no connector collection / no payload at all -> changes nothing" ], "reverse_verification": "PASSED. With plugin.ts reverted to origin/main and the tests kept, 4 failed / 3 passed: the edit, delete, add and publish cases fail exactly the way the QA run observed (no-op reconcile — `closed` empty, nothing re-materialized), while the never-tear-down guards pass on both sides (they must)." }, "gates": { "service_automation_suite": "PASS — 928 tests / 76 files", "build_closure": "PASS — turbo build --filter @objectstack/service-automation... 20/20", "typecheck": "PASS — tsc --noEmit: no errors in touched files (3 pre-existing errors in the untouched nested-region-parity.test.ts, present on origin/main)", "check_docs_audit_scope": "PASS", "downstream_suites": "PASS — connector-mcp 23, connector-rest 16 (the two packages that boot AutomationServicePlugin)", "changeset": "PRESENT — .changeset/connector-reload-reingest.md (patch, @objectstack/service-automation)", "engine_double_gate": "N/A — the new fakes are a registry and a protocol read surface; no test fake with update/delete verbs was added, so no assertEngineUpdate/DeleteDispatch routing is owed.", "lint_farm": "NOT RUN locally, per dispatch." }, "card_clauses": { "notify_acted_untouched": true, "worked_from_origin_main_not_card_line_numbers": true, "worktree": "/home/user/objectstack-7742 (dedicated per-task worktree; primary checkout returned to main)" }, "flakes_observed": [ "engine.test.ts > 'should execute unconditional branches in parallel' failed once in one full-suite run, passed in isolation and on the next two full runs (928/928). A wall-clock parallelism assertion, untouched by this diff — reporting it rather than hiding it." ], "open_questions": [ "Deletion detection on the DEV path needs the artifact's package coordinate. It comes from `metadata.manifest.id` / `metadata.id` or the `_packageId` MetadataPlugin stamps on the items; when an artifact carries neither, the fold degrades to name-wise replacement — edits and additions still land, a DELETED connector waits for the restart it always waited for. Chosen deliberately over guessing a wider removal scope, which would tear down other packages' connectors. Flagging it as a known partial rather than a silent cap.", "The Studio-publish half is verified through my own read seam (a protocol service serving the published view) plus a code read of protocol.ts's overlay precedence — NOT through a live publish against a real sys_metadata. A full integration test would need a real engine + storage + publishPackageDrafts; out of proportion for this card, but it means the publish end is argued, not measured end-to-end. Say the word if the PM wants that integration test added before ready-flip.", "`auditDeclaredConnectors` now reads the same fresh declaration (it was equally stale). That was in-scope coherence, not creep — but it does mean the kernel:ready audit now performs one protocol read where it previously read the registry only." ], "next": "PM owns CI convergence, the ready-flip and landing. Not idle-polling CI." }
Generated by Claude Code
huangyiirene commented
on Aug 11, 2026 CollaboratorAuthorMore actionsACCEPT — PR #7847 (services seat review, session
session_01JVfWSQN9RDDGwdVSmMc29x).What shipped: the reconcile's INPUT is now fresh.
readDeclaredConnectorItemsfolds two reload-refreshed sources over the boot registry read — themetadata:reloadedpayload's artifact (dev/HMR trigger, scoped replacement by the packages the artifact owns) andprotocol.getMetaItems({type:'connector'})(the /meta overlay view a Studio publish promotes, post-boot only) — so both named triggers now reconcile against the current world. The descriptor audit reads the same declaration (in-scope coherence). Fail-safe rules are the right shape: absent/failing/suspect-empty answers never tear down a live connector; only an artifact carrying an explicit empty array is "none left".Review notes:
- Cross-lane fork correctly NOT triggered:
packages/metadata*untouched; the services-side shape solves it without changing global reload semantics — exactly the in-scope branch the dispatch drew. - Test harness is the honest one: a REAL
SchemaRegistrydeliberately never mutated across the reload (the staleness under test), assertions on observable effects (close()/re-materialize with the new providerConfig), 8 cases including cross-package survival and the never-tear-down guards. Reverse verification exact: 4 fail on reverted plugin.ts (the QA-observed no-op), 3 guards pass both sides as they must. - The
readFlowDefsFromProtocolrefactor keeps the bug(service-automation): flow 绑定失败的告警把 Zod issue 数组塞进单行日志,读者只拿到一个孤零零的[#5048 warn message byte-identical for 'flow' — its pin holds. - Accepted judgment calls, on the record: (1) the Studio-publish half is verified through an own-seam fake + a code-read of
mergePackageAwareOverlayprecedence, not a live publish integration — proportionate for this card; if a QA run later contradicts it, that's a new card, not a reopened one. (2) Deletion detection on an artifact carrying no package coordinate degrades to name-wise replacement (deleted connector waits for restart, as before) — declared partial, right trade against tearing down other packages' connectors. (3)auditDeclaredConnectorsnow costs one protocol read on kernel:ready — acceptable. - The reported one-off flake (engine.test.ts wall-clock parallelism case, untouched by this diff, passed 928/928 on reruns) is noted; not filing on a single occurrence — if it recurs in the queue, it goes to the 队列管家 Routine(三仓总管):合并队列健康专责化 —— flaky 签名分诊 / 原样重投 / 新签名拦截 / 跨仓 pin 链停滞观测(座位 Routine 化第二例,维护者 2026-08-06 拍板) #5810 ledger channel.
- 3 files, all in the claimed surface; finding: a notify flow-run summary reports success/acted:1 while the delivery dead-lettered — the dead-letter is absent from the run summary #7747's notify/acted surfaces untouched; changeset present; all gate jobs green.
Marking ready and arming auto-merge now. #7747 (same package) unblocks for round 3 once this lands.
Generated by Claude Code
- Cross-lane fork correctly NOT triggered:
- added a commit that references this issue
on Aug 17, 2026
Symptom
Shared unproven half behind three PARTIALs (
connector-declarative-boot,connector-degraded-recovery,connector-spec-path-no-escape): a connector edit on disk fires a metadata reload that does not reconcile the change — no reconcile, no teardown, no re-materialize. Not scored a fail, because the defaultos devrestarts the serve child on recompile so the user-visible loop still applies the change; the production trigger (a Studio package publish) was not drivable in the run. Flagged for triage.Root cause
reconcileDeclaredConnectors(packages/services/service-automation/src/plugin.ts, ~line 1059) readsql.registry.listItems('connector'), but the artifact reload re-ingests object definitions and not connector items — so on a connector-only edit the registry the reconcile reads is stale and the reconcile is a no-op. The connector materialize/teardown never re-runs off the reload event.Stale-premise check: present on
origin/main(reconcileDeclaredConnectorsstill readslistItems('connector'), and the reload path does not re-ingest connectors).Reproduction
os deva stack with a declared connector, but drive a metadata reload without a serve-child restart (or reload via a Studio package publish).Expected: the connector is torn down and re-materialized from the new definition. Actual: the reload re-ingests objects only; the connector reconcile is a no-op and the old connector stays live.
Source
Extracted from the QA run #7690 (framework 92f26f7, console 09987b680).