fix(task-queue): derive the queue name once, and stamp it into the served manifest - #3101
Conversation
…rved manifest
The Temporal task-queue name was computed twice, independently, by two code
paths that must agree exactly and were not forced to: the worker's
_derive_task_queue(), and the manifest served to the Automation Engine, which
token-filled `atlan-{app_name}-{deployment_name}`. When they disagreed nothing
failed loudly — AE submitted to one queue, the worker polled another, and the
run sat unclaimed until its 24h heartbeat backstop (CONNECT-183; the same gap
stripped failure attribution in HYP-1954).
The rule now lives in one place, application_sdk.common.task_queue, and the
manifest route does not re-run it: it stamps the queue the handler was
configured with, which is the same value create_worker receives. Two paths
deriving the same answer is a convention that holds until their inputs differ;
one path copying the other's answer is structural. That also covers what
re-derivation cannot reach — an explicit ATLAN_TASK_QUEUE override, and a baked
contract name that no longer matches the deployment's ATLAN_APPLICATION_NAME.
The queue template is matched and replaced as a unit rather than token-filled.
Filling in place is what produced the divergence: with ATLAN_DEPLOYMENT_NAME
unset the worker drops the prefix and polls a bare `<app>`, while token-filling
yields `atlan-<app>-local`.
The unset case stays loud. constants.APPLICATION_NAME's "default" is scoped to
identity uses (object-store prefixes, log tagging) and excluded from queue
naming: `atlan-default-prod` reads as a legitimate queue, is polled by nobody,
and reproduces the original hang, whereas a literal `{app_name}` in the DAG is
greppable and diagnosable in one step. An unbaked token that the SDK can fill
from the registered app name is filled and logged at WARNING; with nothing to
fill it from, the token is served as-is and logged at ERROR.
Behaviour preserved deliberately:
- `{ClassName}-queue` when no app name exists — it predates the env-var
convention and is load-bearing for local dev.
- DAG nodes dispatching to another app's queue (`atlan-publish-{deployment_name}`)
are token-filled and otherwise left alone; they are legitimately not this
app's queue.
- The `{deployment_name}` token's existing fallback for non-queue uses.
The e2e agent_spec mirror and the handler's own `{app_name}-queue` default now
route through the same helper, so neither is a fresh implementation of the rule.
FND-195
📜 Docstring Coverage ReportRESULT: PASSED (minimum: 30.0%, actual: 78.4%) Detailed Coverage ReportThis message was truncated. Download full message |
📦 Trivy Vulnerability Scan Results
Report Summary
Scan Result Detailspackages/conformance/uv.lockrequirements.txtuv.lock |
📦 Trivy Secret Scan Results
Report Summary
Scan Result Detailspackages/conformance/uv.lockrequirements.txtuv.lock |
☂️ Code Coverage
Overall Coverage
New FilesNo new covered files... Modified FilesNo covered modified files...
|
The three manifest-serving branches were left with two behaviours: the per-entrypoint and root-disk paths route through _resolve_manifest_placeholders, while a programmatic AppManifest was serialised and served verbatim. That is the drift FND-195 exists to remove, reintroduced one branch over. The programmatic branch is the shape that needs it most, not least: its DAG is hand-built in Python, so there is no contract-toolkit bake behind it at all and every "the toolkit already resolved this" argument is inapplicable. create_app_handler_service(manifest=...) is public API, so this is a reachable path, and an unresolved queue template there lands in AE exactly as it would from disk. Also records why a runtime fill exists alongside the toolkit's bake, in the task_queue module docstring: #2270 proposed it, #2271 superseded it by moving the fix into App.pkl, #2478 extended that to NativeApp.pkl. The bakes are correct; what they cannot reach is adoption lag (apps with already-committed manifests that have not regenerated) and writers outside the toolkit's generation step (Heracles, native-migration-app, atlan-local-marketplace-app's install-time DAG rewrite). Without that context the module reads as a second mechanism competing with the toolkit, and the next reader re-opens a settled question. Refs FND-195. Supersedes the substitution in #3097, which used constants.APPLICATION_NAME and so manufactured "default" for the unset case.
|
@sdk-resolve |
|
🤖 SDK Resolve started. Driving this PR toward merge-ready — fixing CI + every This runs out-of-band and can take several minutes; I'll comment here when it finishes. |
|
@sdk-review |
Earlier @sdk-review trigger (click to expand)🔍 SDK Review (mothership) triggered by @mothership-ai[bot] at 2026-08-10T17:24:11.236Z. Watch the workflow run live — the review summary will appear as a separate comment when complete (typical: 5–30 min, hard cap 2h). ✅ Completed — status |
SDK Review (mothership): PR #3101 — fix(task-queue): derive the queue name once, and stamp it into the served manifestVerdict: NEEDS FIXES
Findings
Holistic Recommendations
Strengths
CI: no checks reported at review time |
|
🤖 SDK Resolve — converged (round 3). The latest re-review on head |
…ic file The dynamic-manifest hook replaces the manifest wholesale — `raw = orjson.dumps(computed)` discarded the reconciled bytes — so everything it emitted was served unreconciled: a task_queue, a freshly generated node, a token it re-introduced. FND-195's guarantee that the served queue is stamped from what the worker polls therefore did not hold for any app with a hook. That is the worst possible population to miss. A bundle app's marketplace entry points have their DAG computed per submission by exactly this hook, and those are the CONNECT-183-shaped apps the guarantee exists for. #3090 widens the exposure further: before it, a bare /manifest on a bundle 404'd; after it, that route serves hook-computed DAGs through this same path. Not a regression introduced here — the pre-FND-195 code also substituted {deployment_name} before the hook and kept nothing afterwards. It only becomes load-bearing once the queue value is the thing being guaranteed. The pre-hook pass stays, so the hook still sees resolved values it may key on; the second pass is idempotent (no template survives the first, so it is a no-op unless the hook introduced something new). Catches unresolved *tokens*, not a hook that hardcodes a concrete-but-wrong queue: that string has no token to match, and normalising every `atlan-*` queue would rewrite the legitimate cross-app dispatch nodes this deliberately leaves alone. Conformance O005 (#3094) is the guard for that shape. Both tests were checked against the pre-change code and fail there. The unresolvable-name test asserts the graded outcome — deployment token filled, {app_name} left visible — because asserting only the surviving token would also pass against a build that never reconciles hook output. Refs FND-195.
…titution
The whole-manifest byte rewrite in resolve_manifest_tokens under-delivered on
the PR's own invariant in two ways, both the same class (unscoped byte
substitution):
* the residual {app_name}/{deployment_name} passes ran over the whole manifest
after the queue stamp, so a configured queue containing literal token text
(custom-{deployment_name}-queue via ATLAN_TASK_QUEUE) was mutated post-stamp
into a queue no worker polls while resolution.task_queue reported the
original;
* the queue-template rewrite matched atlan-{candidate}-{deployment_name}
anywhere in the bytes, so a foreign-app DAG node whose baked queue matched a
candidate, or a description string holding the template text, was re-pointed
at this app's worker queue.
Parse the manifest and rewrite only values the manifest itself labels
task_queue, at any depth; fill the residual tokens in every other string in the
same walk so the fills never touch an already-stamped queue. A configured queue
is now stamped verbatim, and a manifest too malformed to parse falls back to
the pre-FND-195 byte behaviour with the stamp ordered before the fills.
Also pin each precedence/override row to an independent hard-coded expectation
rather than the coupled worker/manifest oracle, and cover the multi-node and
any-depth shapes the single-node tests missed.
|
@sdk-review |
Earlier @sdk-review trigger (click to expand)🔍 SDK Review (mothership) triggered by @mothership-ai[bot] at 2026-08-10T17:52:27.574Z. Watch the workflow run live — the review summary will appear as a separate comment when complete (typical: 5–30 min, hard cap 2h). ✅ Completed — status |
|
@sdk-review |
SDK Re-review (mothership): PR #3101 — fix(task-queue): derive the queue name once, and stamp it into the served manifestVerdict: NEEDS FIXES
Delta from previous review
Findings
Holistic Recommendations
Strengths
CI: all passing |
Earlier @sdk-review trigger (click to expand)🔍 SDK Review (mothership) triggered by @mothership-ai[bot] at 2026-08-10T18:22:55.066Z. Watch the workflow run live — the review summary will appear as a separate comment when complete (typical: 5–30 min, hard cap 2h). ✅ Completed — status |
|
@sdk-resolve |
|
🤖 SDK Resolve started. Driving this PR toward merge-ready — fixing CI + every This runs out-of-band and can take several minutes; I'll comment here when it finishes. |
…bstitution
A manifest too malformed to parse fell back to whole-manifest byte
substitution: stamp the queue template, then run the residual {app_name} /
{deployment_name} replaces over the result. The stamp could not be scoped
away from those fills, so a configured queue carrying literal token text
(custom-{deployment_name}-queue via ATLAN_TASK_QUEUE) was stamped and then
mutated by the later pass while resolution.task_queue still reported the
original — the served manifest and the worker disagreed on the queue. The
same path also re-pointed foreign-node and description bytes.
Serve the malformed manifest back unstamped and log at ERROR instead. Such
a manifest is one the build-time validation never saw — already broken, and
a loud failure beats a silently wrong queue. Delete _stamp_task_queue_bytes
and correct the docstring's ordering claim, and add a regression test for a
token-carrying override through the fallback.
O005 fired five times on the module that implements the behaviour it is meant to protect. Run against PR #3101 (FND-195): common/task_queue.py:86 APP_NAME_TOKEN = "{app_name}" common/task_queue.py:239 attribute docstring naming the token common/task_queue.py:243 attribute docstring naming the token handler/service.py:2009 logger.error(... unresolved {app_name} ...) handler/service.py:2023 logger.warning(... unbaked {app_name} ...) None can freeze into an identifier. A rule that flags the canonical fix, the docs describing it, and the logs diagnosing it is a rule people suppress — which costs the true positives too. Detection now anchors on the token reaching a value. Three exclusions added, each narrow: * documentation — the value of any bare string expression statement. The previous check excluded only body[0] of Module/ClassDef/FunctionDef, so a PEP 257 attribute docstring (a bare string after a field annotation) was flagged. A string bound to nothing cannot be dispatched. * diagnostic text — inside a logging call, warnings.warn, or a raise. Reporting an unresolved token requires quoting it. * token sentinels and message constants — bound to an ALL_CAPS name where the literal is exactly the token (its own definition) or the name reads as prose (_MESSAGE, RATIONALE). Kept narrow deliberately: TASK_QUEUE = "atlan-{app_name}-prod" is ALL_CAPS but neither bare-token nor prose-named, so it still fires. New tests pin that, plus keyword arguments, values at any depth in a DAG literal, a returned template, and a bare token bound to a lowercase name — the shapes an over-broad exclusion would have swallowed. All ten original tests pass unchanged. Also realigned the rule metadata, which predated FND-195 and claimed no canonical helper exists. application_sdk.common.task_queue now provides derive_task_queue and resolve_manifest_tokens, so remediation has one target. Detection stays shape-anchored rather than import-anchored on purpose: the writers most worth catching are hand-authored templates outside the SDK that import nothing at all. The checker's own _MESSAGE is now built from _TOKEN rather than spelling the token inline, so this module does not depend on its own exclusions to avoid self-flagging. Rule docs regenerated. Note for a follow-up, not addressed here: O005 is claimed by both this PR and #3089 (DirectRocksdictImport), both targeting 0.18.0 — whichever merges second needs renumbering, including the {#o005} docs anchor. Deciding which yields is the two authors' call.
|
@sdk-review |
Earlier @sdk-review trigger (click to expand)🔍 SDK Review (mothership) triggered by @mothership-ai[bot] at 2026-08-10T20:22:51.442Z. Watch the workflow run live — the review summary will appear as a separate comment when complete (typical: 5–30 min, hard cap 2h). ✅ Completed — status |
SDK Re-review (mothership): PR #3101 — fix(task-queue): derive the queue name once, and stamp it into the served manifestVerdict: READY TO MERGE
Delta from previous review
Findings
Holistic Recommendations
Strengths
CI: all passing |
atlan-ci
left a comment
There was a problem hiding this comment.
SDK reviewer's verdict: READY TO MERGE.
Full review summary is in the comment posted on this PR.
…path log The malformed-manifest fallback runs both when json.loads raises and when parsing succeeds with a scalar root (null, a number, a string), but the ERROR always said the manifest "does not parse as JSON" — inaccurate for valid JSON with a non-container root and liable to send an operator hunting a syntax error that isn't there. Track the parse failure separately and say "scalar root (no object to walk)" for the well-formed-but-unwalkable case.
|
@sdk-review |
Earlier @sdk-review trigger (click to expand)🔍 SDK Review (mothership) triggered by @mothership-ai[bot] at 2026-08-10T20:52:55.885Z. Watch the workflow run live — the review summary will appear as a separate comment when complete (typical: 5–30 min, hard cap 2h). ✅ Completed — status |
SDK Re-review (mothership): PR #3101 — fix(task-queue): derive the queue name once, and stamp it into the served manifestVerdict: READY TO MERGE
Delta from previous review
FindingsNo findings. The delta is clean: control flow is mutually exclusive, scalar roots ( Holistic Recommendations
Strengths
CI: all passing |
…ediation target Addresses the round-1 review on #3094 (4 findings): * Escaped-brace f-strings (f"{{app_name}}") are no longer exempt: the braces are not interpolated, so the runtime value carries the literal token — the exact frozen shape the rule exists to catch. The JoinedStr waiver is line-keyed (positions survive the SARIF emitter's re-parse); a docstring or diagnostic f-string on the same line stays exempt, and a resolving f-string quoting the token in a log call is never re-flagged. * The prose-name exclusion now matches the trailing delimited segment (_MESSAGE, START_MESSAGE, VALIDATION_RATIONALE) instead of any substring, so queue templates whose names merely contain a prose fragment (MESSAGE_QUEUE, HELP_QUEUE, DOC_QUEUE) fire again. * The runtime SARIF message and the rule rationale/description no longer send users to application_sdk.common.task_queue as if it existed on main — that helper ships with FND-195 (#3101, unmerged). The message now leads with the always-available fixes (f-string / .format(app_name=...)) and names the helper as the canonical target only once the FND-195 release lands. * optimizations.prose.md gains the O005 fix prescription (the generic O-series remediation dispatch routes O005 here): in-scope resolution, threading from a caller, the version-gated helper, and when a cross-file-resolution suppression is legitimate. Dogfood under the gate's exclude scope is byte-identical to the reviewed HEAD (13 known testing/ hits, all WARN-tier). 28 O005 tests pass, including new regressions for each finding.
✅ SDK Resolve — merge-ready@cmgrote — your turn. This PR is merge-ready; a human merges (I never do). No reviewer list is configured ( Outcome: green CI + zero
Nothing remains open. The reviewer's two optional cosmetic candidates (rewording a trailing sentence for the scalar-root case; a test-hardening assertion) were explicitly withdrawn under the re-review convergence rules and are not findings — safe to pick up any future time the file is touched. |
|
@sdk-review |
|
🔍 SDK Review (mothership) triggered by @cmgrote at 2026-08-10T21:11:22.351Z. Watch the workflow run live — the review summary will appear as a separate comment when complete (typical: 5–30 min, hard cap 2h). ✅ Completed — status |
SDK Re-review (mothership): PR #3101 — fix(task-queue): derive the queue name once, and stamp it into the served manifestVerdict: READY TO MERGE
Delta from previous review
No code changed since the prior review of this same HEAD ( FindingsNo findings. The diff is byte-identical to the one previously reviewed and approved: the malformed-manifest ERROR distinguishes a genuine parse failure ("does not parse as JSON") from valid JSON with a scalar root ("parses as JSON but has a scalar root (no object to walk)"), both branches are pinned by regression tests, and no Temporal contract or determinism surface is touched. Holistic Recommendations
Strengths
CI: all passing |
atlan-ci
left a comment
There was a problem hiding this comment.
SDK reviewer's verdict: READY TO MERGE.
Full review summary is in the comment posted on this PR.
O005 fired five times on the module that implements the behaviour it is meant to protect. Run against PR #3101 (FND-195): common/task_queue.py:86 APP_NAME_TOKEN = "{app_name}" common/task_queue.py:239 attribute docstring naming the token common/task_queue.py:243 attribute docstring naming the token handler/service.py:2009 logger.error(... unresolved {app_name} ...) handler/service.py:2023 logger.warning(... unbaked {app_name} ...) None can freeze into an identifier. A rule that flags the canonical fix, the docs describing it, and the logs diagnosing it is a rule people suppress — which costs the true positives too. Detection now anchors on the token reaching a value. Three exclusions added, each narrow: * documentation — the value of any bare string expression statement. The previous check excluded only body[0] of Module/ClassDef/FunctionDef, so a PEP 257 attribute docstring (a bare string after a field annotation) was flagged. A string bound to nothing cannot be dispatched. * diagnostic text — inside a logging call, warnings.warn, or a raise. Reporting an unresolved token requires quoting it. * token sentinels and message constants — bound to an ALL_CAPS name where the literal is exactly the token (its own definition) or the name reads as prose (_MESSAGE, RATIONALE). Kept narrow deliberately: TASK_QUEUE = "atlan-{app_name}-prod" is ALL_CAPS but neither bare-token nor prose-named, so it still fires. New tests pin that, plus keyword arguments, values at any depth in a DAG literal, a returned template, and a bare token bound to a lowercase name — the shapes an over-broad exclusion would have swallowed. All ten original tests pass unchanged. Also realigned the rule metadata, which predated FND-195 and claimed no canonical helper exists. application_sdk.common.task_queue now provides derive_task_queue and resolve_manifest_tokens, so remediation has one target. Detection stays shape-anchored rather than import-anchored on purpose: the writers most worth catching are hand-authored templates outside the SDK that import nothing at all. The checker's own _MESSAGE is now built from _TOKEN rather than spelling the token inline, so this module does not depend on its own exclusions to avoid self-flagging. Rule docs regenerated. Note for a follow-up, not addressed here: O005 is claimed by both this PR and #3089 (DirectRocksdictImport), both targeting 0.18.0 — whichever merges second needs renumbering, including the {#o005} docs anchor. Deciding which yields is the two authors' call.
…ediation target Addresses the round-1 review on #3094 (4 findings): * Escaped-brace f-strings (f"{{app_name}}") are no longer exempt: the braces are not interpolated, so the runtime value carries the literal token — the exact frozen shape the rule exists to catch. The JoinedStr waiver is line-keyed (positions survive the SARIF emitter's re-parse); a docstring or diagnostic f-string on the same line stays exempt, and a resolving f-string quoting the token in a log call is never re-flagged. * The prose-name exclusion now matches the trailing delimited segment (_MESSAGE, START_MESSAGE, VALIDATION_RATIONALE) instead of any substring, so queue templates whose names merely contain a prose fragment (MESSAGE_QUEUE, HELP_QUEUE, DOC_QUEUE) fire again. * The runtime SARIF message and the rule rationale/description no longer send users to application_sdk.common.task_queue as if it existed on main — that helper ships with FND-195 (#3101, unmerged). The message now leads with the always-available fixes (f-string / .format(app_name=...)) and names the helper as the canonical target only once the FND-195 release lands. * optimizations.prose.md gains the O005 fix prescription (the generic O-series remediation dispatch routes O005 here): in-scope resolution, threading from a caller, the version-gated helper, and when a cross-file-resolution suppression is legitimate. Dogfood under the gate's exclude scope is byte-identical to the reviewed HEAD (13 known testing/ hits, all WARN-tier). 28 O005 tests pass, including new regressions for each finding.
Problem
The Temporal task-queue name was computed twice, independently, by two code paths that must agree exactly and were not forced to:
main._derive_task_queue(), readingATLAN_APPLICATION_NAME/ATLAN_DEPLOYMENT_NAMEand branching on truthiness;task_queuethe Automation Engine writes into the DAG and therefore submits work to — token-filled fromatlan-{app_name}-{deployment_name}at serve time.When they disagree nothing fails loudly. AE submits to one queue, the worker polls another, and the run sits unclaimed until its 24h heartbeat backstop — CONNECT-183. The same gap stripped failure attribution in HYP-1954, and has been independently hand-patched at least four times outside this repo (Heracles, native-migration-app,
atlan-local-marketplace-app/ CONNECT-191,atlan-hightouch-app/ ARUN-1039), plus one shipped double-prefix bug (DISTR-834) from a caller that prefixed an already-prefixed value.Given
ATLAN_DEPLOYMENT_NAME=prod, only the first row was safe:ATLAN_APPLICATION_NAMEdbtatlan-dbt-prodatlan-dbt-proddbt, no deployment namedbt(bare, unprefixed)atlan-dbt-default<ClassName>-queueatlan-default-prodThe other two produce a plausible-looking queue name that nothing polls.
Fix
One derivation. New
application_sdk/common/task_queue.pyholds the rule.main._derive_task_queue, the manifest-serve path,create_app_handler_service's own{app_name}-queuedefault, and the e2eagent_specmirror all route through it — four in-repo implementations collapsed to one.The manifest doesn't re-derive, it stamps. Two paths deriving the same answer only agree while their inputs agree, and the inputs are exactly what drifts. The handler passes
resolve_manifest_tokensthe queue it was configured with — the same valuecreate_workerreceives, in both combined and split-deployment mode — and the servedtask_queueis stamped from it. That closes two cases plain re-derivation cannot reach:ATLAN_TASK_QUEUE/--task-queueoverride (no derivation reproduces it);ATLAN_APPLICATION_NAME— the DISTR-834 / CONNECT-191 shape, where the manifest serves a plausibleatlan-dbt-prodwhile the worker pollsatlan-dbt-v3-prod.The hook's output is reconciled too, not just the static file.
compute_manifestreplaces the manifest wholesale, so reconciling only the file on disk left everything the hook emitted unreconciled — and a bundle's marketplace entry points have their DAG computed per submission by exactly that hook, i.e. the guarantee was missing for the population it exists for. The pre-hook pass stays (the hook should see resolved values it may key on) and the second pass is idempotent. It catches unresolved tokens, not a hook that hardcodes a concrete-but-wrong queue — that has no token to match, and normalising everyatlan-*queue would rewrite the legitimate cross-app dispatch nodes below; #3094's O005 is the guard for that shape.The queue template is matched as a unit, not token-filled. Filling
{app_name}and{deployment_name}separately is the divergence: with no deployment name the worker drops the prefix and polls a bare<app>, while filling in place yieldsatlan-<app>-local.The unset case stays loud.
constants.APPLICATION_NAME's"default"is now documented as scoped to identity uses (object-store prefixes likepersistent-artifacts/apps/<name>/, log tagging) where a missing segment would break paths — and excluded from queue naming, sinceatlan-default-prodreads as a legitimate queue, is polled by nobody, and reproduces the original hang. Loudness is graded, because one token hides two different failures:{app_name}the SDK can fill from the registered app name (what the toolkit would have baked) is filled and logged at WARNING — the response is correct, but the app's committed manifest is stale and the next writer of that DAG gets no backstop;{app_name}in the DAG is greppable and diagnosable in one step.Deliberately preserved
{ClassName}-queuewhen no app name exists — it predates the env-var convention and is load-bearing for local dev. The manifest now stamps that same queue, so a local full-DAG run lands where the local worker actually polls.atlan-publish-{deployment_name}, and the QI / popularity / lineage nodes inApp.pkl) are token-filled and otherwise left alone — not normalised, and not warned about, since a warning there would fire on every correct multi-app DAG.{deployment_name}token's existing fallback for non-queue uses.Not in this PR
No contract-toolkit change. Both baking the fully-derived value and the inverse (a single
{task_queue}token the SDK resolves) change the manifest wire format, and the four out-of-repo consumers that hand-patched this gap substitute{app_name}/{deployment_name}themselves — emitting a token they don't know about breaks them. That's a coordinated cross-repo rollout, not a line here. Keeping the wire format and moving reconciliation into the serve path gets the same guarantee with no downstream break; worth revisiting once those consumers are retired.Relationship to open PRs
constants.APPLICATION_NAME, which manufactures"default"for the unset case; the equivalent substitution here prefers the registered app name and refuses to invent one. The one thing it covered that this PR had missed — the programmatic-manifest branch — is now folded in (see the test plan below), and its fix(handler): fill manifest app_name from APPLICATION_NAME so failure logs surface #2270 → fix(contract-toolkit): bake manifest app_name from contract name; drop runtime placeholder #2271 → fix(contract-toolkit): bake NativeApp manifest app_name from contract name #2478 rationale for why a runtime fill exists alongside the toolkit's bake is preserved incommon/task_queue.py's module docstring.task_queue). Merge it first: it unblocks a production break and should not wait on this hardening PR, and since it dropped its bare-manifest disk glob in review its only remaining overlap here is a blank-line deletion beside a line this PR edits — a trivial rebase, and mine to absorb rather than its author's. Correcting an earlier version of this bullet: with that glob dropped, a bare/manifeston a bundle no longer serves acompute_manifest-computed DAG, so fix(handler): filesystem-resolve bundle marketplace entrypoints for input-contract and bare-manifest #3090 does not widen traffic through the hook path. The hook-output reconciliation below stands on its own for apps called with an explicit?entrypoint=, which is how AE submits.Test plan
tests/unit/common/test_task_queue.py, written to assert equality between the worker and manifest paths rather than two independently hard-coded expectations — so a future change to the rule can't satisfy one side and quietly break the other. Covers all three rows of the table, both the un-baked and toolkit-baked template shapes, the explicit-override and baked-name-disagrees cases, and the residual-token behaviour.tests/unit/handler/test_service.py: served queue matches the configured worker queue when the baked name disagrees with env, and unresolvable{app_name}keeps the literal token and never manufacturesdefault— each asserted on all three manifest sources: the disk path, the programmaticAppManifestpath (which previously servedmodel_dump_json()verbatim), and thecompute_manifestoutput (which previously discarded the reconciled bytes viaraw = orjson.dumps(computed)). The two hook tests were both checked against the pre-change code and fail there; the unresolvable-name one asserts the graded outcome — deployment token filled,{app_name}left visible — since asserting only the surviving token would also pass against a build that never reconciles hook output.uv run pytest tests/unit— 6369 passed, 9 skippeduv run pre-commit run --files …(ruff, ruff-format, isort, pyright) — cleanCloses FND-195.
🤖 Generated with Claude Code