Skip to content

Add policyengine-api-access and stop sending browser code to the Public API - #116

Draft
anth-volk wants to merge 18 commits into
mainfrom
dashboard-api-skill
Draft

anth-volk wants to merge 18 commits into
mainfrom
dashboard-api-skill

Conversation

@anth-volk

@anth-volk anth-volk commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

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-client 0.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-v2 main at 6030be089 (licence MIT, dist-tag latest). 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-check is kept.

Changes: the new skill

  • skills/policyengine-api-access/SKILL.md:
    • The rules: browser code never calls api.policyengine.org or the Public API's Cloud Run addresses; no Public API credential or address goes in a NEXT_PUBLIC_* or VITE_* variable; one Auth0 client per calling server.
    • Which route to use: pages, backends, CI jobs and Python analysis.
    • From pages: the four dashboard operations with their request and answer shapes and validation, @policyengine/api-client usage, 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 no Retry-After and 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.
    • From servers: the production and staging tenants, audiences and variables; the token request; Authorization, PolicyEngine-User-Authorization and PolicyEngine-Unauthenticated-User; caching tokens across instances on Modal, Vercel and CI; no redirects; and the steps for getting a client onto PUBLIC_API_SERVER_CLIENT_IDS before any token is sent.
    • Local development and previews, asking for a new dashboard operation, and a table of the dashboard API's and the Public API's errors, including 401, 403, 429 and 503.
  • 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 (a modal.Dict on 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.
  • The skill is in the analysis-tools (which the create-dashboard workflow installs), api-development, app-development, complete and dashboard-builder bundles 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.json is rebuilt with uv 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 to policyengine-data's description and target repositories. tests/pipeline/test_when_to_use_guide.py reads skill IDs from this file, so docs/when-to-use.md, which now names the skill, needs it. It is rebuilt again at 74182b7, 95936b4 and 2e751e5 for the changed skills, agents and command: their descriptions, sizes and references, and new edges to policyengine-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 /health and /v2/openapi.json instead of /specification and 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 runs policyengine in its own process needs neither.
  • skills/policyengine-app: the calculator's direct browser calls through BASE_URL are moving to app.policyengine.org's own route, /api/policyengine/{Public API path}, which forwards only the operations in app/src/libs/publicApiRoute/allowList.ts. New calculator code goes through that route.
  • targets/claude/agents/dashboard/backend-builder.md and targets/claude/commands/new-tool.md: the Pattern B client wraps @policyengine/api-client instead of fetching https://api.policyengine.org. The types are type aliases, because the client takes Record<string, unknown> and a TypeScript interface is not assignable to it.
  • The dashboard planner, scaffold, integrator and backend-builder load policyengine-api-access and describe Pattern B by what the dashboard API offers. The data_pattern value stays policyengine-api, so existing plan.yaml files still validate.
  • The policyengine skill, targets/codex/agents/policyengine-app-v2/AGENTS.md and docs/when-to-use.md point to policyengine-api-access; the guide's catalog counts are current (25 skills).

Sources

Every path, shape, header, origin rule and error comes from merged code:

  • PolicyEngine/policyengine-app-v2 main at 6030be089, the commit @policyengine/api-client 0.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/, and app/src/models/household/pythonPackageCodec.ts for the axes shape.
  • PolicyEngine/policyengine-api main at 2073325a: policyengine_api/request_caller.py (the credential check runs on every route, /health included), policyengine_api/libs/server_credentials.py, policyengine_api/routes/household_routes.py and services/household_calculation_service.py (/calculate adds no omitted variables, and accepts axes), and docs/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-policy and create-dashboard workflows on GitHub Actions. None sends a credential, so each stops working once the credential requirement is on:

  • targets/claude/agents/microsim-runner.md: Python requests to /us/policy, /us/economy/… and …/budget-window (its default "API path"); the analyze-policy workflow runs it through /analyze-policy.
  • targets/claude/commands/create-dashboard.md: in headless runs, the precomputed pattern fetches impacts with POST /us/policy and the economy endpoints, without naming the host.
  • targets/claude/agents/dashboard/dashboard-planner.md: for the precomputed pattern, a /calculate sweep 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: curl to /us/policy and /us/economy/….
  • targets/claude/agents/parameter-locator.md: curl to /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-builder agent, and the integrator's Step 4 example for a custom backend, use a useHouseholdSimulation hook that backend-builder doesn't export (it exports useCalculation); /deploy-dashboard adds a dashboard's rewrites to website/next.config.ts, while the dashboard API's allowed origins come from website/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.json in app-v2. After this merges, skills goes 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.py checks 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.py checks 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' repr hides the secret, and the Opener Protocol takes the request by position and the timeout by keyword.

  • Red, e84b09c: 53 failed on their assertions, 1 passed (the pattern self-test); exit code 1.
  • Green, 9e63be7: 54 passed; exit code 0. The green commit also registers the template module in sys.modules in the test loader, which dataclasses needs.
  • The firewall's 429 (added after the rules went live): red 5e0fbc1, where tests/test_api_access_skill.py::test_skill_describes_the_dashboard_api_firewall_refusal failed on its assertion (exit code 1); green e8cf0af, 55 passed (exit code 0).
  • Review round 1's findings: red a139568, where test_template_raises_a_refused_request_without_requesting_another_token, test_skill_describes_the_dashboard_api_firewall_refusal and test_errors_table_tells_a_refused_server_token_from_a_refused_user_token failed on their assertions (exit code 1); green 7e325d3, all 74 tests in tests/test_api_access_skill.py and tests/test_public_api_browser_guidance.py passed (exit code 0).
  • The manifest: red 00261a0, where tests/test_api_access_skill.py::test_dashboard_manifest_lists_the_skill failed on its assertion (exit code 1); green 9ac72ca, 56 passed (exit code 0).
  • CI: red 42886437907 at 00261a0: tests/test_api_access_skill.py::test_dashboard_manifest_lists_the_skill failed on its assertion, 1 failed and 263 passed; green 42886481119 at 9ac72ca: 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.md and the template.

The existing guidance

tests/test_public_api_browser_guidance.py: TypeScript and JavaScript examples in skills/, targets/ and docs/ may not name a Public API host (the household API and v2 hosts are not matched); no NEXT_PUBLIC_ or VITE_ variable may be set to one or named like a credential; eleven files that guide dashboard and app work must point to policyengine-api-access; the API skill's public routes; and the tools skill's Pattern B.

  • Red, 29b87d2: 15 failed on their assertions, 2 passed (no public-prefix violations existed, and the patterns' self-test); exit code 1.
  • Green, b6f3e85: 17 passed; exit code 0.
  • Review round 3's findings: red 703a72a, where seven tests in the two new test files failed on their assertions (exit code 1), among them tests/test_public_api_browser_guidance.py::test_calculated_household_is_read_through_a_typed_helper and tests/test_api_access_skill.py::test_template_opener_protocol_takes_the_request_positionally_and_timeout_by_keyword; green 2e751e5, all 89 tests in the two files passed (exit code 0).
  • Type checks, run locally at 2e751e5: the TypeScript in backend-builder's Pattern B and the integrator's Steps 4 and 6 compiles together under strict (TypeScript 5.9) against @policyengine/api-client's source at 6030be089, and the old result.households.household… read fails there with TS18046; the token template passes mypy --strict.
  • Review round 2's findings: red 68e4eaa, where eight tests in the two new test files failed on their assertions (exit code 1), among them tests/test_api_access_skill.py::test_template_modal_wiring_adds_the_module_to_the_image and tests/test_public_api_browser_guidance.py::test_integrator_varies_a_household_in_one_calculation; green 95936b4, all 83 tests in the two files passed (exit code 0), after the skill's test module dropped three checks that duplicated the guard.
  • Review round 1's findings: red f69d880, where tests/test_public_api_browser_guidance.py::test_guards_catch_the_patterns_they_name, ::test_dashboard_scaffold_loads_the_access_skill and ::test_custom_backend_guidance_agrees_with_what_the_dashboard_api_offers failed on their assertions (exit code 1); green 74182b7, 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.
  • CI: these changes were first opened as Send dashboard and app guidance through the dashboard API, not the Public API #117, whose run 42885835654 failed tests/pipeline/test_when_to_use_guide.py::test_skills_section_references_real_skills, because the guide names policyengine-api-access and the dashboard manifest had not been rebuilt. The manifest commits above fix it.

🤖 Generated with Claude Code

anth-volk and others added 6 commits November 10, 2026 22:00
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>
anth-volk and others added 3 commits November 10, 2026 23:33
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>
Folds the former #117 into #116, so one PR adds the skill and stops the
existing skills, agents and commands telling browser code to call the
Public API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk anth-volk changed the title Add policyengine-api-access: reaching the Public API from dashboards and servers Add policyengine-api-access and stop sending browser code to the Public API Nov 10, 2026
@anth-volk
anth-volk requested review from DTrim99 and removed request for DTrim99 November 10, 2026 19:49
anth-volk and others added 8 commits November 10, 2026 23:55
- 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stop skills and agents telling browser code to call the Public API Add a skill for reaching the Public API from dashboards and servers

1 participant