Repository navigation
Conversation
Adds tests/test_api_access_skill.py for a new policyengine-api-access skill. They fail until the skill exists: they check its frontmatter, its place in the five bundles that build dashboards or call the Public API and in the catalog, that the wrapper build ships it, and that it names the dashboard API's four operations, @policyengine/api-client's exports, the server credential headers, tenants and variables, the refusal bodies, and a row for 401, 403, 429 and 503. Browser-language examples may not name a Public API host, and no credential may sit in a NEXT_PUBLIC_ or VITE_ variable. The token template's tests check that one token serves many requests and is shared between instances through the shared store, is replaced five minutes before it expires, that a refused request is retried once with a new token, and that redirects are never followed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and servers The Public API is becoming callable only by PolicyEngine's own servers. The new skill tells dashboard developers and agents how to reach it: - the rules: browser code never calls api.policyengine.org or the Public API's Cloud Run addresses, and no Public API credential or address goes in a NEXT_PUBLIC_ or VITE_ variable; - from pages: www.policyengine.org's four dashboard operations through @policyengine/api-client, with their request and answer shapes, polling, the origins the dashboard API accepts and its rate limits, all taken from policyengine-app-v2's merged website routes, forwarding package and client; - from servers: an Auth0 client-credentials token from the Public API's own tenant in Authorization, one client per caller, cached across instances, and how a client gets on the Public API's allow list before it sends a token; - local development and previews, asking for a new dashboard operation, and a table of the dashboard API's and the Public API's errors. templates/public_api_token.py is a copyable Python token cache for Modal backends, Cloud Run services and scripts: it shares one token between instances, replaces it five minutes before it expires, retries a 401 once with a new token, and never follows redirects. The skill is in the analysis-tools, api-development, app-development, complete and dashboard-builder bundles and the catalog; the plugin version moves to 4.2.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… API Adds tests/test_public_api_browser_guidance.py. It fails until the guidance is updated: TypeScript and JavaScript examples in skills/, targets/ and docs/ may not name a Public API host (api.policyengine.org or its Cloud Run addresses; the household API and the v2 alpha hosts are not matched), no NEXT_PUBLIC_ or VITE_ variable may be set to one or named like a credential, the skills, agents and commands that guide dashboard and app work must point to policyengine-api-access, the policyengine-api skill must list /health and /v2/openapi.json instead of the removed /specification and liveness and readiness checks, and the tools skill's Pattern B must use @policyengine/api-client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…blic API The Public API is becoming callable only by PolicyEngine's servers, so the catalog stops telling browser code to call api.policyengine.org: - policyengine-api says who may call v1 (servers with their own credential), that pages use the dashboard API, and lists /health and /v2/openapi.json instead of the removed /specification and liveness and readiness checks; - policyengine-tools' Pattern B is the dashboard API through @policyengine/api-client, and a Pattern C backend that calls the Public API gets its own client and cached token; - policyengine-app says the calculator's direct browser calls are moving to app.policyengine.org's own allow-listed route, and new code goes through it; - backend-builder and new-tool build Pattern B on @policyengine/api-client; the dashboard planner, scaffold and integrator load policyengine-api-access and describe the pattern by what the dashboard API offers; - the policyengine skill, the app-v2 Codex routing and docs/when-to-use.md point to policyengine-api-access; the guide's catalog counts are current. The data_pattern value policyengine-api is kept, so existing plan.yaml files still validate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Vercel firewall rate limits on www.policyengine.org are live: over a limit the firewall answers 429 itself, as text/plain "Too Many Requests", with no Retry-After and no CORS headers. The new test fails until the skill says so, names the client's 60-second wait for that case, and gives the firewall and the Public API's load balancer a 429 row each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rate limits on www.policyengine.org are live. The skill now says they are counted per IP address in fixed 60-second windows, per Vercel region, and shared by everyone behind one address; that the firewall answers 429 itself, as text/plain "Too Many Requests", with no Retry-After and no CORS headers, so a page on another origin such as localhost sees a network failure; and that requests are accepted again when the window ends. compareEconomy's polling text covers both client versions: one that exports DEFAULT_RATE_LIMIT_WAIT_MILLISECONDS waits 60 seconds after such a 429, and an earlier one waits one interval. The errors table gives the firewall and the Public API's load balancer a 429 row each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 10, 2026
tests/pipeline/test_when_to_use_guide.py reads skill IDs from the committed dashboard/src/data/manifest.json, so the stacked guidance PR, whose guide names the new skill, failed in CI. This test fails until the manifest is rebuilt with the skill and its five bundles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated with `uv run python scripts/build_manifest.py`. Besides the new skill, its bundle entries and edges, and the bundles' 4.2.0 version, the rebuild also picks up what earlier merges left unbuilt: the policyengine-data skill's description and target repositories from #108. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…i-browser-guidance Brings in the rebuilt dashboard manifest, which the when-to-use guide test reads, and the skill's description of the firewall's 429. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Nov 10, 2026
…s-check Brings in the rebuilt dashboard manifest, which the when-to-use guide test reads, and the skill's description of the firewall's 429. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A 401 from the Public API is raised without requesting another token, on every call: a new token would be refused too, and each one counts against the tenant's monthly machine-to-machine allowance. - A redirected token request becomes PublicApiTokenError and stores nothing, and the provider uses the no-redirect opener by default. - The polling text names RATE_LIMIT_WAIT_MILLISECONDS, as merged in PolicyEngine/policyengine-app-v2#1543, and no longer says a 429 counts towards the gateway retries. - The errors table has a 401 row for the server's own token and one for a forwarded user token (error="invalid_token"), and neither says to retry with a new token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The token template no longer answers a 401 with a new token. A 401 means the client is not on the allow list in that environment, or its tenant or audience is wrong, so each new token was refused too and used up the tenant's monthly machine-to-machine allowance, once per call per instance. It now raises the 401, as app-v2's forwarder does, and the unused forget() is gone. The shared store's type is a Protocol with get and item assignment, which modal.Dict and dict both satisfy. - The polling text follows the merged client (app-v2#1543, which the first npm publish includes): RATE_LIMIT_WAIT_MILLISECONDS, 429s that never end polling, the fourth gateway failure since the last answer, and polling stopping with status null on another origin. - The errors table has a 401 row for the server's own token and one for a forwarded user token (WWW-Authenticate: Bearer error="invalid_token"), and the 502 row adds Auth0 refusing the token request and a policies answer with no ID. - Smaller corrections: only the comparison route checks query parameters; the Public API's 401 and 503 bodies have no result field; Cloud Run with a few long-lived instances can keep the token in memory; the Vercel Runtime Cache snippet treats a miss as null or undefined and never sets a zero or negative ttl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ents - The guard's self-test now includes forms it missed: Cloud Run's per-project addresses, ~~~ and four-backtick fences, html, vue, svelte and cjs blocks, a TypeScript block nested in a Markdown block, JSON keys, a || fallback in inline code, and public-prefix names tied to the Public API's client or key. It also includes names it must accept: a browser's own Auth0 sign-in settings, a Mapbox token, and a TypeScript block marked as server code. - The dashboard scaffold must load policyengine-api-access. - The tools skill's Pattern C and /new-tool's advice must not send a parameter reform's economy-wide comparison to a custom Modal backend, since the dashboard API offers it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The guard matches Cloud Run's per-project addresses; ``` and ~~~ fences of any length, closed by the same fence; html, vue, svelte, cjs and astro blocks; examples nested in Markdown blocks; JSON keys and || fallbacks; and public-prefix names that hold a secret or are named for the Public API's address or server client. It no longer flags a browser's own Auth0 sign-in settings or other services' public tokens, and a block marked <!-- server-code --> may name the Public API. Code files in the catalog are checked too. - The tools skill's Pattern C and /new-tool no longer send a parameter reform's economy-wide comparison to a custom Modal backend, which the dashboard API offers; they name structural reforms and microsimulation output beyond that comparison. - The dashboard scaffold loads policyengine-api-access. - backend-builder: the household type fits US and UK entities, the comment says when a dashboard's own *.vercel.app host is allowed, and the tests mock the package, since the client is built when the module loads. The integrator says what the policyengine-api pattern passes and gets back. - The app skill says the route keeps the Public API's paths under /api/policyengine. - dashboard/src/data/manifest.json is rebuilt for the changed skills, agents and command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The token template's Modal wiring adds its module to the image, which Modal 1.0 no longer does on its own; its settings never print the client secret; it keeps working when the shared store fails; a short token is replaced halfway through its life. - The skill says the dashboard API's calculation returns only the variables the household asks for, with null for the year. - The guard catches a fence with a space before its language, ?? fallbacks and unqualified public-prefix token and key names, scans .vue and .svelte files, and accepts the browser sign-in audience and a qualified Mapbox access token. - The integrator varies a household in one calculation with axes, types buildHouseholdDict as Household, and CONTRIBUTING.md documents the server-code marker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Token template: the Modal wiring adds the module to the image with add_local_python_source, which Modal 1.0 no longer does on its own; the settings' repr leaves out the client secret; the opener has a Protocol type, and call_public_api lists every error it raises. - Skill: the dashboard API's calculation adds no omitted variables, so outputs are asked for with null, and a chart varies an input in one calculation with axes, not one per point (30 a minute); the polling waits match the merged client; the first 401 row also names a user route that received no forwarded user credential. - Integrator: a variation chart for the policyengine-api pattern is one calculation with axes, buildHouseholdDict returns Household, and the transformers read the calculated household's own shape. backend-builder's Household type takes axes and any entity group; the scaffold declares client types as type aliases. - Guard: a fence may have a space before its language, ?? fallbacks and unqualified token and key names are caught, .vue and .svelte files are scanned, and *_AUDIENCE names, which hold the sign-in audience, are accepted. The skill's own test module drops its divergent copy of these checks; the guard already scans every skill. CONTRIBUTING.md documents the rule and the <!-- server-code --> marker. - The API skill cites fastapi_routes for its public routes, and the dashboard manifest is rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The token template's Opener Protocol takes the request positionally and the timeout by keyword, as urllib's opener does, and its docstring says how to drop a revoked token. - The skill's household example asks for an output with null. - The guard catches API_ACCESS_TOKEN and API_BEARER_TOKEN names and accepts app-v2's NEXT_PUBLIC_POLICYENGINE_API_V2_URL and a token named for another service. - backend-builder offers a typed readVariable() for the calculated household and the integrator uses it; the integrator debounces form values before calculating and runs the variation chart for the UK too. - The first 401 row no longer says a malformed PolicyEngine-Unauthenticated-User gets the same answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- backend-builder: CalculatedHousehold is Record<string, unknown> with a typed readVariable() helper, since result.households.household... does not compile against an unknown index signature (TS18046 under strict); CalculatedValue includes the lists axes return. The integrator reads results through readVariable(). - Integrator: the policyengine-api pattern calculates from debounced form values (useDebouncedValue, compared by content), keyed in React Query, so typing or dragging sends one calculation per pause instead of one per keystroke against the 30-a-minute limit; the variation chart is debounced too and runs for UK households, which have no state. - Token template: the Opener Protocol takes the request by position and the timeout by keyword, as urllib's opener does; _is_usable is a TypeGuard and the template passes mypy --strict. Its docstring says how to drop a token that Auth0 has revoked. - Skill: the household example asks for household_net_income with null; the first 401 row no longer claims a malformed PolicyEngine-Unauthenticated-User gets the same answer. - Guard: API_ACCESS_TOKEN and API_BEARER_TOKEN names are caught; NEXT_PUBLIC_POLICYENGINE_API_V2_URL and tokens named for another service are accepted. - The dashboard manifest is rebuilt. The examples in backend-builder's Pattern B and the integrator's Steps 4 and 6 type-check together under strict against @policyengine/api-client 0.1.0's source (TypeScript 5.9). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #113
Fixes #114
Summary
The Public API (api.policyengine.org) is becoming callable only by PolicyEngine's own servers. This adds
policyengine-api-access, a skill that tells dashboard developers and agents how to reach it: from pages through www.policyengine.org's dashboard API and@policyengine/api-client, and from servers with their own Auth0 client-credentials token. It also stops the existing skills, agents and commands telling browser code to call the Public API, and points them to the new skill.@policyengine/api-client0.1.0, which the skill tells dashboards to install, is on npm: it was published by hand on 2026-11-10 from PolicyEngine/policyengine-app-v2mainat6030be089(licence MIT, dist-taglatest). That commit includes #1543, so the published client polls as the skill describes.The guidance changes were first opened as #117 and now merge here, with the skill they point to. The CI check script once planned for dashboards (#118, #115) is dropped; its branch
public-api-access-checkis kept.Changes: the new skill
skills/policyengine-api-access/SKILL.md:api.policyengine.orgor the Public API's Cloud Run addresses; no Public API credential or address goes in aNEXT_PUBLIC_*orVITE_*variable; one Auth0 client per calling server.@policyengine/api-clientusage, economy-comparison polling, the origins the dashboard API accepts, and its rate limits as the Vercel firewall applies them since 2026-11-10: over a limit it answers 429 itself, as text/plain, with noRetry-Afterand no CORS headers, so a page on another origin sees a network failure. The polling text follows the client as merged with @policyengine/api-client: wait out the firewall's 429 while polling policyengine-app-v2#1543 (RATE_LIMIT_WAIT_MILLISECONDS), which the published 0.1.0 includes.Authorization,PolicyEngine-User-AuthorizationandPolicyEngine-Unauthenticated-User; caching tokens across instances on Modal, Vercel and CI; no redirects; and the steps for getting a client ontoPUBLIC_API_SERVER_CLIENT_IDSbefore any token is sent.skills/policyengine-api-access/templates/public_api_token.py: a copyable Python token cache for Modal backends, Cloud Run services and scripts. It keeps one token in memory and in a store the instances share (amodal.Dicton Modal) until five minutes before it expires, raises a 401 without requesting another token (a new one would be refused too and use up the tenant's monthly allowance), never follows redirects, and never stores the client secret. It uses only the standard library.analysis-tools(which the create-dashboard workflow installs),api-development,app-development,completeanddashboard-builderbundles and in the catalog. The plugin version moves from 4.1.0 to 4.2.0 in every bundle and the marketplace template.dashboard/src/data/manifest.jsonis rebuilt withuv run python scripts/build_manifest.py: the new skill, its five bundle edges and overlap entries, and the bundles' version and skill lists. The rebuild also picks up Stop the data skill steering agents to microimpute and uk-data #108's unbuilt change topolicyengine-data's description and target repositories.tests/pipeline/test_when_to_use_guide.pyreads skill IDs from this file, sodocs/when-to-use.md, which now names the skill, needs it. It is rebuilt again at74182b7,95936b4and2e751e5for the changed skills, agents and command: their descriptions, sizes and references, and new edges topolicyengine-api-access.Changes: the existing skills, agents and commands
skills/policyengine-api: says who may call v1 (servers with their own credential) and that pages use the dashboard API; drops "JS frontends" from when to use REST; lists/healthand/v2/openapi.jsoninstead of/specificationand the liveness and readiness checks, which the Public API removed (policyengine_api/migration_flags.py).skills/policyengine-tools: Pattern B is the dashboard API through@policyengine/api-client, with its four operations. A Pattern C backend that calls the Public API needs its own client and cached token; one that runspolicyenginein its own process needs neither.skills/policyengine-app: the calculator's direct browser calls throughBASE_URLare moving to app.policyengine.org's own route,/api/policyengine/{Public API path}, which forwards only the operations inapp/src/libs/publicApiRoute/allowList.ts. New calculator code goes through that route.targets/claude/agents/dashboard/backend-builder.mdandtargets/claude/commands/new-tool.md: the Pattern B client wraps@policyengine/api-clientinstead of fetchinghttps://api.policyengine.org. The types aretypealiases, because the client takesRecord<string, unknown>and a TypeScriptinterfaceis not assignable to it.policyengine-api-accessand describe Pattern B by what the dashboard API offers. Thedata_patternvalue stayspolicyengine-api, so existingplan.yamlfiles still validate.policyengineskill,targets/codex/agents/policyengine-app-v2/AGENTS.mdanddocs/when-to-use.mdpoint topolicyengine-api-access; the guide's catalog counts are current (25 skills).Sources
Every path, shape, header, origin rule and error comes from merged code:
mainat6030be089, the commit@policyengine/api-client0.1.0 was published from (it includes #1533 and #1543):website/src/app/api/,website/src/lib/dashboardApi/(operations.ts,cors.ts,handler.ts),packages/public-api-forwarding/(settings.ts,accessToken.ts,cors.ts,responses.ts),packages/api-client/, andapp/src/models/household/pythonPackageCodec.tsfor theaxesshape.mainat2073325a:policyengine_api/request_caller.py(the credential check runs on every route,/healthincluded),policyengine_api/libs/server_credentials.py,policyengine_api/routes/household_routes.pyandservices/household_calculation_service.py(/calculateadds no omitted variables, and acceptsaxes), anddocs/migration/stage-12a-user-login.md(the user routes' 401).Left as they are
These call the Public API outside a browser: from an agent's shell, or from this repository's
analyze-policyandcreate-dashboardworkflows on GitHub Actions. None sends a credential, so each stops working once the credential requirement is on:targets/claude/agents/microsim-runner.md: Pythonrequeststo/us/policy,/us/economy/…and…/budget-window(its default "API path"); theanalyze-policyworkflow runs it through/analyze-policy.targets/claude/commands/create-dashboard.md: in headless runs, theprecomputedpattern fetches impacts withPOST /us/policyand the economy endpoints, without naming the host.targets/claude/agents/dashboard/dashboard-planner.md: for theprecomputedpattern, a/calculatesweep over the lever inputs at build time.targets/claude/commands/analyze-policy.md: checks a parameter's deployed value through the metadata endpoint and probes/calculate.targets/claude/agents/model-corroborator.md:curlto/us/policyand/us/economy/….targets/claude/agents/parameter-locator.md:curlto/us/metadata.targets/claude/commands/text-to-reform.md: names the Public API as where a user might submit a reform.dashboard/src/pages/Guides.tsx: guide text describing microsim-runner's API path; it makes no request.analyses/*.md: dated records, kept as written.Not in this PR
Dashboard guidance that disagrees with itself but doesn't send browser code to the Public API, found in review rounds 2 and 3: the
frontend-builderagent, and the integrator's Step 4 example for a custom backend, use auseHouseholdSimulationhook thatbackend-builderdoesn't export (it exportsuseCalculation);/deploy-dashboardadds a dashboard's rewrites towebsite/next.config.ts, while the dashboard API's allowed origins come fromwebsite/src/data/appZoneRoutes.ts; and the planner's household-page rule ("Hybrid compute (the NJ pattern)") sends custom household inputs to a Modal backend rather than Pattern B. Each needs a decision about the dashboard workflow, so they are left for a follow-up.policyengine.org/us/ai-agents shows the catalog's counts from
website/src/app/[countryId]/ai-agents/pluginStats.jsonin app-v2. After this merges,skillsgoes from 24 to 25 there, in an app-v2 PR.Open: where developers ask for a new dashboard operation and for a server client. The skill says an issue in PolicyEngine/policyengine-app-v2 and "the Public API's maintainers"; this waits for a decision.
Tests
The skill
tests/test_api_access_skill.pychecks the frontmatter; the five bundles, the catalog and the wrapper build; that the skill names the four operations, the client's exports, the server headers, tenants, audiences and variables, the 401, 503 and 403 bodies, the allowed origins, and a row for each of 401, 403, 429 and 503.tests/test_public_api_browser_guidance.pychecks every skill, this one included, for browser-language examples that name the Public API and for public-prefix variables that hold its address or a credential. The template's tests use a fake opener and a dict as the shared store: one token serves many requests and both instances, it is replaced five minutes before expiry, a 401 is raised on every call without another token request, a redirected token request becomes an error and stores nothing, partial settings are refused, a local server's 307 is not followed, a failing shared store does not stop it, a short token is replaced halfway through its life, the settings'reprhides the secret, and theOpenerProtocol takes the request by position and the timeout by keyword.e84b09c: 53 failed on their assertions, 1 passed (the pattern self-test); exit code 1.9e63be7: 54 passed; exit code 0. The green commit also registers the template module insys.modulesin the test loader, whichdataclassesneeds.5e0fbc1, wheretests/test_api_access_skill.py::test_skill_describes_the_dashboard_api_firewall_refusalfailed on its assertion (exit code 1); greene8cf0af, 55 passed (exit code 0).a139568, wheretest_template_raises_a_refused_request_without_requesting_another_token,test_skill_describes_the_dashboard_api_firewall_refusalandtest_errors_table_tells_a_refused_server_token_from_a_refused_user_tokenfailed on their assertions (exit code 1); green7e325d3, all 74 tests intests/test_api_access_skill.pyandtests/test_public_api_browser_guidance.pypassed (exit code 0).00261a0, wheretests/test_api_access_skill.py::test_dashboard_manifest_lists_the_skillfailed on its assertion (exit code 1); green9ac72ca, 56 passed (exit code 0).00261a0:tests/test_api_access_skill.py::test_dashboard_manifest_lists_the_skillfailed on its assertion, 1 failed and 263 passed; green 42886481119 at9ac72ca: both jobs passed. The earlier commits were pushed before the PR existed, so their red runs are the local ones above.The anti-rot lint's checks pass on every changed file. The wrapper build ships
skills/policyengine-api-access/SKILL.mdand the template.The existing guidance
tests/test_public_api_browser_guidance.py: TypeScript and JavaScript examples inskills/,targets/anddocs/may not name a Public API host (the household API and v2 hosts are not matched); noNEXT_PUBLIC_orVITE_variable may be set to one or named like a credential; eleven files that guide dashboard and app work must point topolicyengine-api-access; the API skill's public routes; and the tools skill's Pattern B.29b87d2: 15 failed on their assertions, 2 passed (no public-prefix violations existed, and the patterns' self-test); exit code 1.b6f3e85: 17 passed; exit code 0.703a72a, where seven tests in the two new test files failed on their assertions (exit code 1), among themtests/test_public_api_browser_guidance.py::test_calculated_household_is_read_through_a_typed_helperandtests/test_api_access_skill.py::test_template_opener_protocol_takes_the_request_positionally_and_timeout_by_keyword; green2e751e5, all 89 tests in the two files passed (exit code 0).2e751e5: the TypeScript inbackend-builder's Pattern B and the integrator's Steps 4 and 6 compiles together understrict(TypeScript 5.9) against@policyengine/api-client's source at6030be089, and the oldresult.households.household…read fails there with TS18046; the token template passesmypy --strict.68e4eaa, where eight tests in the two new test files failed on their assertions (exit code 1), among themtests/test_api_access_skill.py::test_template_modal_wiring_adds_the_module_to_the_imageandtests/test_public_api_browser_guidance.py::test_integrator_varies_a_household_in_one_calculation; green95936b4, all 83 tests in the two files passed (exit code 0), after the skill's test module dropped three checks that duplicated the guard.f69d880, wheretests/test_public_api_browser_guidance.py::test_guards_catch_the_patterns_they_name,::test_dashboard_scaffold_loads_the_access_skilland::test_custom_backend_guidance_agrees_with_what_the_dashboard_api_offersfailed on their assertions (exit code 1); green74182b7, all 77 tests in the two new test files passed (exit code 0). The guard now also covers~~~and longer fences, HTML, Vue and Svelte blocks, nested blocks, JSON keys,||fallbacks and Cloud Run's per-project addresses, and accepts a browser's own Auth0 sign-in settings.tests/pipeline/test_when_to_use_guide.py::test_skills_section_references_real_skills, because the guide namespolicyengine-api-accessand the dashboard manifest had not been rebuilt. The manifest commits above fix it.🤖 Generated with Claude Code