Skip to content

permitio 3.0.0: refactor SDK APIs, tests, and release validation - #134

Open
zeevmoney wants to merge 139 commits into
mainfrom
per-15318/fix-checkalltenants-request
Open

zeevmoney wants to merge 139 commits into
mainfrom
per-15318/fix-checkalltenants-request

Conversation

@zeevmoney

@zeevmoney zeevmoney commented Jun 28, 2026 •

Copy link
Copy Markdown
Member

Summary

This release corrects permission-check payloads and context handling, makes SDK errors and logging safer, and moves the test suite to Vitest. It also sets the supported runtimes to Node >=22.13.0, replaces Yarn with pinned pnpm 12.8.1, and checks authored and generated code with strict TypeScript, Oxlint and Oxfmt.

Tracking: PER-16556. This description covers the implemented changes in this PR.

SDK behavior

  • checkAllTenants sends an authenticated POST with the permission query in the body, normalizes users/resources, merges context, and does not inject a default tenant (PER-15318).
  • bulkCheck respects each item's context, followed by method and global context, without mutating caller inputs (PER-16492).
  • PDP HTTP errors and unreadable successful responses produce PermitPDPStatusError; connection failures remain PermitConnectionError. With throwOnError: false, failed bulk checks return one denial per input and all-tenant checks return an empty list (PER-16494).
  • Constructor debug logging omits the serialized configuration. JSON and in-process pretty logging work without exposing the token or requiring a worker thread (PER-16493).
  • REST failures use named errors with detached, bounded diagnostics. SDK-owned errors and logs omit credentials, private attributes and raw socket/request objects while preserving useful failure descriptions and valid status/code metadata (PER-16544, PER-16565).
  • Native WHATWG URL handling replaces url-parse. Invalid or empty PDP URLs fail during construction; dot segments are normalized. This includes @Kyzgor's contribution from refactor(deps): replace url-parse with the native URL API (PER-16497) #122 and closes url-parser is an unnecessary dependency #106.

Runtime and dependency management

  • Support Node >=22.13.0. Unit/tooling and packed-consumer CI test 22.13.0, 22.23.3, 24.0.0, 24.21.0 and 26.11.0; package metadata, contributor docs and release evidence use the same minimum (PER-16557, PER-16943).
  • The migration scanner accepts supported semver subsets and stable runtime pins. Unsupported, unversioned, digest-only and unresolved Node Docker stages require review; platform-qualified images and prior stage aliases are handled explicitly (PER-16943).
  • pnpm 12.8.1 uses an exact lockfile, exact direct dependency pins, strict engine checks, a 24-hour publication delay and disabled dependency lifecycle scripts.
  • Builds and hook setup are explicit. pnpm commands replace Yarn and the incompatible task sequencer; unused dependencies and obsolete scripts are removed.
  • Axios 1.20.0, lodash 4.18.1 and the logging dependency tree pass production and fresh-consumer audits with zero findings.
  • Published CommonJS/ESM entry points are preserved; the reviewed OpenAPI rebaseline changes one runtime enum name as documented below. The committed package version is now 3.0.0; npm publication remains blocked by the acceptance requirements below.

Dependency refresh (PER-16944)

  • Run the pinned OpenAPI Generator 7.25.0 JAR directly with Java after verifying its recorded SHA-256. Remove the npm wrapper and its unused dependency tree. Generated SDK output remains identical.
  • Refresh compatible fast-copy and source-map-js dependencies. Locked SDK, production and fresh packed-consumer audits report zero advisories without exclusions or overrides.
  • Distinguish missing Java, executable permission errors and generator output overflow. Preserve the cause and bounded diagnostics; regression tests exercise actual subprocess failures.

Strict tooling and declaration integrity (PER-16558)

  • TypeScript 7 checks authored and generated source with strict optional properties, indexed access, overrides and module syntax. Generated @ts-ignore suppressions are removed; normalization has syntax/configuration checks and a final compiler gate.

  • Oxlint, Oxfmt and worktree-scoped prek hooks replace ESLint, Prettier and Husky. pnpm verify runs the frozen dependency check, lint, formatting, strict types, both builds and local tests. CI runs these checks as explicit required candidate jobs, followed by packed-consumer and security gates.

  • Authored source uses ESM and absolute aliases. esbuild creates the existing CommonJS/ESM entry points; TypeScript emits declarations and a checked AST pass resolves aliases to portable package paths. A private TypeScript 6 workspace supplies the compiler API used by TypeDoc and AST tooling.

  • Two missing resource-instance bulk models and five existing model exports now complete the generated bulk declarations. They come from the existing pinned schema, and a strict consumer regression catches missing exports.

  • Empty condition-set-rule creation responses produce a clear error instead of returning a missing value.

  • Contributor instructions, editor recommendations and agent instructions describe the implemented tools and checks.

  • Local-role strict consumer tests have a 35-second per-case deadline above their existing 30-second compiler limit. Hosted CI can finish both TypeScript checks without weakening assertions or changing global timeouts (PER-16956).

Reviewed OpenAPI contract (PER-16560)

  • Regeneration uses a reviewed, hashed OpenAPI 3.1 snapshot with 263 public operations, plus the existing Elements login route. Shared local/CI configuration validates the source and reproduces the committed output from two clean generations.
  • The generated runtime enum EnvironmentCopyConflictStrategyEnum becomes EnvironmentCopyConflictStrategy. Required tenant fields, current role models and generated-only removals are documented in the migration inventory.
  • High-level condition-set, tenant, resource-instance and relationship-tuple lists return arrays by default for raw-array or envelope responses. Resource-relation lists unwrap the backend envelope to match their array contract.
  • Role-assignment and assigned-role singleton filters retain exact encoded, unbracketed query values. Explicit API/PDP destinations retain precedence over an injected Axios default URL.
  • Bulk user and relationship-tuple results use unspecified object types because the backend result schemas remain empty. Request payloads are no longer presented as response contracts; this does not claim bulk response schemas are fixed.
  • Narrow source corrections and the Elements supplement are checked against exact expected shapes. Source-spec defects are tracked separately; unsupported generated-only routes are inventoried rather than silently retained.

PDP response contracts (PER-16562)

  • Validate unknown PDP and OPA response bodies before returning authorization data. Decisions must be literal booleans and bulk responses must contain one decision per dispatched input; caller-array mutation cannot alter response or fallback cardinality.
  • Validate permission entries and granted tenants while preserving additive fields and legal dictionary keys. Apply documented missing permission/attribute defaults; nullable optional details are omitted. Any malformed or denied tenant entry invalidates the entire list.
  • Malformed responses raise PermitPDPStatusError or use the existing configured denial fallback. Per-call error policy remains supported where already exposed, and a zero timeout is preserved.
  • Unsupported useOpa: true for bulk and permission calls produces an explicit SDK error before HTTP instead of being ignored. check() retains direct OPA support. The README documents these next-major behavior changes.

HTTP ownership and retries (PER-16563)

  • Keep caller-supplied Axios clients unchanged. Private SDK transports delegate each attempt through the caller's live adapters, hooks and transforms while keeping SDK destinations, credentials, logging and retry settings separate between instances.
  • Configured SDK URLs and Bearer tokens take precedence over caller URL and Basic-auth defaults. This includes allowAbsoluteUrls: true for SDK requests; direct caller requests retain their own settings. Intentional caller hooks and caller-owned retries remain under caller control.
  • SDK retries require both the original and actual request methods to be eligible. REST POST/PATCH are never replayed; supported idempotent PUT/DELETE retries and PDP/OPA authorization POST retries remain opt-in.
  • Stop retries on cancellation or invalid configuration before invoking custom predicates. Honor cancellation added by caller hooks during backoff, preserve transient-error classification, and handle Retry-After names case-insensitively.
  • Validate retry options and bound delay arithmetic. Failed attempts and backoff consume one Axios timeout budget; zero disables that budget. REST/Elements use caller timeout settings and PDP/OPA use the SDK timeout. Arbitrary caller hooks/adapters are not interrupted by a hard wall-clock deadline.
  • Remove superseded interceptor helpers and the unused axios-retry dependency. The coverage extractor recognizes the specific internal transport declaration; its reviewed baseline delta preserves every public method and source-operation denominator.

Mixed Axios entries (PER-16669)

  • Preserve live caller default headers when SDK and supplied Axios clients use different CommonJS/ESM exports. SDK requests keep explicit destinations, Bearer credentials and retry policy.
  • Forward headers as an own-property record while retaining false/null suppression values. Request/response hooks and transforms run once per attempt; direct caller requests keep their settings.
  • Cover every SDK-entry/caller-export combination through REST and OPA, including header changes after construction and during a retry.

Configuration and API context (PER-16564)

  • Validate effective JavaScript constructor options before creating loggers or transports, with useful errors that omit rejected values. TypeScript callers keep complete Axios clients, contexts and callback types.
  • Snapshot and freeze SDK-owned settings, including nested logging, tenancy and retry arrays. The public config binding is readonly at runtime; caller-owned clients and functions remain live and editable.
  • Copy supplied API contexts into independent SDK state, preserving validated permissions and initial selection. CommonJS and ESM package entries accept each other's contexts without sharing mutable state.
  • Share one scope lookup across concurrent API wrappers within an SDK. Failed initialization can retry, malformed hierarchy cannot partially initialize state, and late completion cannot overwrite an explicit context selection. Original callers still receive their failed lookup's error.
  • Document the next-major ownership and constructor-validation changes. The removed recursive-partial helper is replaced by explicit option types.

Base URL validation (PER-16683)

  • Reject literal query and fragment delimiters in apiUrl and pdp, including empty trailing delimiters, before creating SDK loggers or transports. Errors identify the option without retaining rejected values.
  • Validate effective PERMIT_API_URL and PERMIT_PDP_URL defaults while preserving valid explicit overrides and explicit-undefined fallback.
  • Preserve HTTP(S), ports, IPv6 and ordinary or encoded path prefixes. The SDK does not strip or rewrite rejected URL components. Update configuration comments, README, the paired migration guide and generated reference.

Safe errors and diagnostics (PER-16565)

  • Normalize REST failures as PermitApiError, with useful HTTP status or transport codes and detached causes. Empty, text and validation responses produce readable descriptions.
  • Redact credentials, cookies and private request values before bounding descriptions, URLs and retained response fields. Malformed adapter status metadata is omitted. SDK errors do not retain raw socket objects, request bodies or caller stack/cause graphs.
  • Preserve the original caller error object and retry callback inputs. Bounded privacy context follows reused errors between SDK instances and both package entries without retaining requests.
  • Keep PDP and context failures named and actionable; logs describe operations and decisions without serializing user attributes or permissions. REST response diagnostics are typed as unknown, and the misleading generic error-body parameter is removed.
  • Document the changed diagnostic contract and test strict consumers, serialized errors, real logger output and encoded private-value echoes.

User-list options (PER-12643; GitHub #83)

  • Expose typed searchOperator and includeResourceInstanceRoles on users.list. Operators are startswith, endswith and contains; explicit false is serialized and omitted options keep the API defaults.
  • Preserve existing search, role and pagination behavior and the full PaginatedResultUserRead envelope, including nested resource-instance roles. Existing generated dispatch already supports the parameters; runtime files are unchanged by this unit.
  • Update the README, strict CommonJS/ESM consumer checks and generated reference. The reviewed API inventory changes only the two option members; all source-operation and method counts remain unchanged.

Tenant list totals (PER-11295)

  • Preserve the tenant array for omitted or false includeTotalCount; true returns the complete PaginatedResultTenantRead with metadata and selected attribute types. Dynamic or optional flags retain the array/page union.
  • Keep filtering, pagination, API/PDP routing and wait-for-sync clones. Snapshot options before asynchronous scope lookup so caller mutation cannot change dispatch or result selection. No pagination defaults or counts are synthesized.
  • Cover complete, empty and additive envelopes, explicit false and omitted queries, HTTP failures, strict installed consumers, and actual filtered/page counts through both local routes.

Attribute result types (PER-16504; GitHub #82)

  • Add optional caller-selected attribute types to all 22 direct user, tenant and resource-instance result paths. Default attributes remain optional object; named interfaces, literal unions, readonly members and nullable inner values retain their declared shape.
  • Preserve full user/detailed-instance pages, sync's { user, created } envelope, getter aliases and waitForSync clones. Tenant membership returns the selected user attributes. Existing role-list parameter generics and flag inference remain unchanged.
  • Keep the generic wrappers outside generated source. The README and complete public reference describe caller declarations without promising runtime response validation or inference from write payloads. Runtime code, dependencies, routes and operation counts are unchanged by this unit.

User invites (PER-12882)

  • Add permit.api.userInvites with direct selected-environment list, create, get, full-body PATCH, delete and approve operations. Facts proxy settings do not reroute these calls or promise PDP synchronization or email delivery.
  • Expose required nullable invite fields, complete pagination envelopes and the actual nullable user approval result. Internal invite IDs remain distinct from user keys; explicit attribute types describe caller expectations without runtime validation.
  • Preserve filters captured before asynchronous scope discovery, caller inputs and additive response fields. Approval HTTP 400 errors omit remote text and bodies that may disclose another stored email, while preserving safe status, transport code, route and actionable guidance.
  • Cover real local invite lifecycle, failed approval, read-only access and independent cleanup of invitations, users, grants, memberships and the temporary scoped key. Deleting an invite does not claim to revoke earlier approval facts.

Groups API (PER-16566)

  • Add permit.api.groups with the eight approved GA operations: create, delete, direct list/get, user membership assignment/removal and resource-role assignment/removal.
  • Accept qualified resource:instance keys or internal IDs for group identifiers. Membership and role-removal DELETE requests retain their required JSON bodies and selected environment context.
  • Preserve the full direct-list pagination envelope, including counts, and distinguish mutation GroupRead results from direct GroupReadSchema results with internal IDs. Strict CJS/ESM consumers verify required bodies and precise inferred result types.
  • Document group creation, filters, membership and role grants. Group-to-group membership remains deferred to later 3.x; EAP and deprecated Groups reads remain unexposed.
  • Add eight exposed GA routes while preserving all 307 source operations and the generated declarations/models. The Groups unit adds eight public HTTP methods.

Membership, detailed lists and PDP refresh (PER-16567)

  • Add tenants.addUser(tenantKeyOrId, userData) to create a new user in a tenant without requiring a role. An existing user remains a duplicate-user error.
  • Add dedicated listDetailed() methods for role assignments, resource instances and relationship tuples. Preserve full nested envelopes, total counts, optional page counts, nulls and additive fields. Pagination defaults to page 1 and 100 rows; instance searches retain repeated query values.
  • Add pdps.refresh({ reason }) for the selected environment. Its response acknowledges submission, not completion. Individual-PDP refresh remains deferred.
  • Route these five operations through the control plane even with facts proxying or waitForSync, preserving selected context and repeated filters without promising PDP synchronization. Existing methods retain their routing.
  • Correct role-assignment list return types for literal flags, dynamic booleans and conditional object unions without changing requests. The compiler inventory identifies explicit control-plane clients and preserves generated models and source operations.

PDP discovery and request context (PER-16568)

  • Add request context to the existing fifth getUserPermissions() config argument, preserving its filters and internal global-then-call context precedence.
  • Add getAuthorizedUsers() with the complete resource, tenant and user-assignment envelope. The method follows the published container/cloud route contract; real-service validation uses the isolated container.
  • Add container-only getUserTenants() for role-derived tenant discovery. Preserve tenant metadata and documented defaults. An unavailable endpoint returns an actionable status-preserving error even when denial fallback is configured.
  • Add filterObjects() using one bulk authorization request. Return original objects in order, preserving duplicates and extra application metadata; send only supported resource fields and apply each object's context over the call context. Empty input sends no request, while invalid/sparse positions and unsupported OPA mode reject before dispatch.
  • Validate complete discovery responses before returning them. Malformed or operational failures honor explicit denial fallback without returning partial grants. New discovery/filter methods always reject unsupported OPA mode; check() retains direct OPA support.
  • Preserve legal JSON dictionary keys in context and resource attributes without changing caller prototypes. Enforcement bodies are serialized before Axios; custom OPA request hooks and transforms receive JSON text and may parse, edit and reserialize it. Direct requests made through the caller's client retain their behavior.
  • Circular references and BigInt inputs produce a safe, contextual SDK error before HTTP, with the existing configured denial fallback. Raw input and serialization exceptions are not retained in diagnostics.
  • Keep the compiler-derived request shapes visible through the narrowly verified internal serializer. Generated operations/models and all 307 source operations remain unchanged.

API and PDP coverage evidence (PER-16561)

  • Compiler-based inventory maps current public HTTP methods and local helpers to generated dispatches and published API/PDP operations. Supporting scope lookups do not count as exposed wrappers.
  • Keep all 307 published operations in the denominator: 263 control-plane, 34 pinned-container and 10 cloud-PDP operations. Reports distinguish exposed, generated-only, supporting-only and missing operations, including explicit lifecycle conflicts and reasoned Node-local decisions.
  • Track signatures, request/response shapes, overloads, defaults, constants, constructor helpers and shared generated dispatch. Source checks retain server overrides, named schema keys and reusable components; unsupported referenced Path Items fail explicitly.
  • pnpm verify includes the offline contract gate. A weekly/manual workflow checks only the two allowlisted public schema documents and retains bounded drift reports with GitHub failure notifications.
  • Local integrity, coverage gaps, shared-target availability and backend evidence are separate results. The shared machine-readable target remains unavailable under PER-16345; this change does not claim cross-SDK parity or backend conformance.

Dependency security gates (PER-16559)

  • Remove obsolete standard-version tooling and its vulnerable dependency tree. Native version validation preserves semantic-version normalization, rerun and disabled-lifecycle safeguards; a release tag must match the committed package version.
  • Pinned pnpm and Trivy scan the locked production tree, full development tree and two independently resolved consumers of the exact packed SDK. Direct dependencies are exact pins; minimum/newest lanes do not claim to test minimum transitive versions.
  • Fixable HIGH/CRITICAL findings block the gate. Scanner failures, malformed reports, empty inventories and incomplete dependency graphs are INVALID. Other findings remain visible in JSON and Markdown evidence.
  • PRs and weekly/manual runs execute the scans on both supported Node floors. Publication requires both floor lanes, then scans and publishes the same final versioned tarball with scripts disabled.
  • Grouped dependency updates use cooldowns. Dependabot's documented pnpm support currently ends at version 10, so pnpm 12 lockfile updates remain unverified; scheduled audits run independently of the bot. Slack delivery awaits an authorized destination.

Tests and CI

Test execution and fixture integrity (PER-16569)

  • Require explicit queued mock responses, preserve null responses, and fail tests for unexpected requests or unused HTTP fixtures even when application code swallows the network failure.
  • Exercise public REST APIs over real loopback HTTP for concurrent scope discovery, failure recovery, cancellation, encoded CRUD identifiers, HTTP failures, pagination and persisted bulk results.
  • Verify bulk tuple IDs and stored user/tuple state instead of accepting write acknowledgments alone. Controlled service fixtures prove that missing writes, unchanged replacements and invalid IDs fail the actual tests.
  • Produce native Vitest reports and validate discovered files, named projects, executed tests and consistent result counts. Unexpected, duplicated or incorrectly nested skips fail. Missing primary backend credentials report UNAVAILABLE before importing tests; specifically approved optional omissions remain explicit limitations.
  • Execute an environment-key integration test in every configured backend lane. Optional organization/project tests use correctly scoped credentials when present; whitespace-only optional keys are treated as absent.
  • Register owned fixture cleanup before writes, preserve a supplied project, and independently verify absence after deletion. Lost write responses and cleanup failures have explicit regressions.
  • Measure all authored runtime TypeScript while excluding generated/test files. Contributor docs describe the implemented test commands and report semantics; no artificial 100% coverage claim is introduced.

Grouped APIs and legacy removal (PER-16570)

  • Remove the 29 deprecated flat API calls and api.getMethods(). Use the existing grouped clients; the migration guide lists every replacement and the changed argument/result shapes.
  • Preserve legacy condition-set type filtering through conditionSets.list({ type }) and unfiltered rule listing through conditionSetRules.list(). Rule filters are independently optional, and pagination remains available.
  • Remove DeprecatedApiClient, the three IDeprecated* interfaces, ContextTransform, and the deprecated ApiContext.level alias. permittedAccessLevel remains the permission-level property. Public runtime and strict CJS/ESM tests reject removed names and compile every grouped replacement.
  • Make ApiClient inherit the shared base directly, eliminating seven duplicate generated clients. Remove unused transform registration, dictionary/regex functions and internal method-bag code. Modern context, error, privacy and grouped-client checks remain.
  • Include MIGRATION.md in the tarball and regenerate the API reference. The new guide's OpenAPI inventory link resolves to a published repository copy for installed-package readers.
  • The reviewed inventory removes the 29 deprecated flat HTTP methods and their obsolete exports, and adds the optional grouped filter interface. Generated methods, models and all 307 source operations remain accounted for.

Packed release evidence (PER-16571)

  • Add pnpm check:release-evidence to bind supplied execution evidence to a clean SDK source tree, the exact packed candidate, the official npm 2.7.5 baseline and both installed consumer lockfiles. A fresh local build must reproduce the candidate archive byte for byte; version labels alone cannot establish identity.
  • Validate every public method and all 307 published operations against the reviewed method, case and phase catalogs. Require actual positive assertion counts, matching proof levels, complete runtime/PDP cells and case totals bounded by their containing phase. A successful phase cannot supply missing case evidence.
  • Distinguish PASS, FAIL and INVALID. Preserve failed positions when evidence is incomplete; missing execution, wrong artifacts, unsuccessful setup or cleanup, zero-assertion success and unknown fields cannot pass. The checker runs locally without contacting backend services.
  • Use explicit public field allowlists and safe diagnostics. Native report hashes are unsigned producer attestations, so the producer and its comparisons still require review. Selected baseline comparisons preserve semantic values and keep intentional migration changes explicit.
  • Keep the remaining unproved add/retain operations and unavailable shared parity target visible. Complete local test execution does not turn those gaps into coverage or release approval; releaseReady remains false.

Migration guide and customer agent skill (PER-16572)

  • Ship a 24-ID migration guide and matching unreleased notes covering runtime/dependency requirements, all 29 flat-to-grouped mappings, result shapes, constructor settings, errors, wire contracts, types and optional additions. Distinguish the official npm 2.7.5 baseline from behavior retained from reviewed main.
  • Include an installable customer agent skill in the package. Its read-only scanner identifies supported Permit imports, aliases, removed calls and changed result consumers; known SDK values crossing unsupported analysis boundaries require manual review. Incomplete source coverage cannot report a clean scan.
  • Pin the scanner's standalone TypeScript compiler and lockfile separately from SDK runtime dependencies. The copied skill installs with frozen dependencies and disabled lifecycle scripts; the scanner does not execute or write customer code or contact services.
  • Compile and run strict CommonJS/ESM migration fixtures against the actual packed SDK, including real loopback request and result assertions. Normal repository verification installs its test copy explicitly rather than relying on an author's local compiler.
  • Correct reference text about condition-rule filter keys and role-derived tenant discovery. Preserve SDK runtime behavior and the existing entry points.

Versioned package and required gates (PER-16573; PER-16506)

  • Commit version 3.0.0 and select genuine ESM declarations for import and CommonJS declarations for require. The ESM facade forwards the canonical declaration graph, preserving private-member ApiContext compatibility across both entries.
  • Validate strict installed customers with TypeScript 6 and 7 under Node16, NodeNext and Bundler resolution. Check actual runtime entry points, cross-entry contexts, the shipped migration scanner and copied guide links against the supplied archive.
  • Prepare isolated customer locks explicitly, audit before frozen scripts-disabled installation, and preserve the copied migration compiler lock. Cold runners do not require a populated dependency-metadata cache.
  • Build one candidate archive from clean committed source. Bind its version, metadata, file inventory, hash and source identity, then pass those exact bytes to both supported-floor consumer/security jobs and the publisher. Publishing never rebuilds or repacks the candidate.
  • Require lint, types, unit/tooling, workflow, generated-contract, consumer and security gates. Always-run aggregate checks reject failed, skipped, cancelled or missing dependencies. Backend cleanup attempts every owned deletion and fails when any deletion cannot complete.
  • Require normalized release tags to match the committed version before the scripts-disabled npm version rerun. Production OIDC publishing requires computed Node publication acceptance; no approval flag replaces required execution, rollout, cleanup, handoff or release-quality proof.
  • Document the proposed required-check ruleset rollout and separate production/tag/Trusted Publisher owner actions. These external settings have not been changed.

Generated JSON arrays and descriptions (PER-16682)

  • Map unique JSON arrays to Array in the checked generator configuration. MonthlyUsage.monthly_tenants now matches Axios JSON responses instead of advertising JavaScript Set methods. Keep the captured UUID item schema, uniqueness constraint, default and source bytes intact.
  • Apply exactly eight checked description corrections during schema preparation, then regenerate the affected models and public reference. Reject upstream drift before replacement and preserve all source-operation, generated-operation, model and public-method counts.
  • Check the actual source-generated Axios response and physical installed declarations separately. The usage models are shipped internal declarations; this change adds no root exports, supported deep imports or organizations facade.

Checkout hook isolation (PER-16685)

  • Install pinned hooks in each checkout's private Git directory before selecting its hook path. Primary and linked checkouts preserve shared default/custom hooks and sibling configuration.
  • Exercise actual Git commits, repeated installation and failed installation across primary/linked checkouts and default/relative/absolute shared paths. Failed installation preserves the previously selected hook path.
  • Update contributor instructions. This tooling change does not alter shipped SDK files.

Public API reference (PER-13613)

  • Export 28 existing contracts used by public inputs, results and errors, including resource-relation/resource-role interfaces, their constructors and request/results, conditional pagination results, check configuration and named context errors. Existing shapes and method behavior remain unchanged.
  • Derive reference groups from the actual public API interface. Verify navigation and field links for every group, plus local page, fragment and asset targets; real missing-link/export controls must fail.
  • Check named root imports in actual packed CommonJS/ESM consumers with both maintained compilers and all three supported resolution modes. Rebuild the complete reference including user invites.
  • Align README, migration guide, release notes, contributor instructions and the shipped migration skill with the current public exports. State the breaking explicit-false pretty-logging behavior and its synchronous write cost. Website deployment remains a separate workflow and owner action.

Reference website workflow (PER-13616)

  • Make the generated public reference a required candidate check. Documentation build/link failures, skipped or missing documentation jobs prevent the candidate and SDK required checks from passing.
  • Add a separate manual Pages workflow that checks the trusted repository, main branch and exact selected commit before checkout. The read-only build audits locked dependencies, installs without lifecycle scripts, and rebuilds the actual public reference.
  • Bind a clean source checkout and actual reference file bytes to the commit, workflow run and attempt. Changed content, symlinks, hard links and excluded repository metadata fail validation before the uniquely named artifact is uploaded.
  • Limit Pages/OIDC write permissions to the deployment job, which executes only the pinned official deployment action. Website deployment remains separate from npm publication.
  • Document the owner prerequisites for switching the existing legacy main:/docs publisher to Actions, protecting the Pages environment, approving deployment and checking the hosted source marker. No Pages settings, workflow dispatch or website publication were performed for this change.

User-attribute schema APIs (PER-16942)

  • Expose permit.api.userAttributes with typed list, get, create, update and delete methods using the existing public attribute contracts and selected API context.
  • Preserve partial update bodies, pagination, scoped identifiers and existing error handling. Export the grouped interface and constructor and regenerate the public reference.
  • Validate both installed module formats and the full create/read/update/list/delete lifecycle against isolated local services. Local PATCH acceptance used a separately prepared server fix for an existing failure; rollout of that server fix remains a dependency.

Typed bulk roles (PER-16948)

  • Expose permit.api.roles.bulkCreateOrReplace for tenant and resource role definitions through the existing public bulk-role endpoint and DTOs.
  • Return the complete received created and updated arrays, preserving scoped routing, context and errors. Document that PUT replay can change the response classification; no batch atomicity or ordering guarantee is added.
  • Cover full request bodies, negative cases and strict installed ESM/CommonJS consumers. This existing role-result contract is separate from the nine empty bulk-response contracts deferred under PER-9298.

URL authorization (PER-16945)

  • Add permit.checkUrl(user, httpMethod, url, options) for the published container PDP /allowed_url operation, with typed tenant, context, timeout and error-policy options.
  • Validate absolute URI inputs before dispatch and preserve accepted URL text. Snapshot caller inputs before asynchronous work, merge context and preserve an explicit zero timeout.
  • Require a direct boolean allow response. Invalid inputs, unsupported OPA mode, malformed responses and unavailable endpoints remain visible even when ordinary transport-error fallback is enabled.
  • Update the README, public API reference, strict installed consumers and reviewed API/evidence catalogs. Public implementation is independently authored from public contracts; private source and raw test reports remain outside this repository.

Local PDP role assignments (PER-16946)

  • Add getLocalRoleAssignments(query?, config?) and dedicated exported query/result types for the container PDP's local assignment cache. Preserve complete rows, additive fields, row order and duplicates.
  • Support user, role, tenant, resource and resource-instance filters, with explicit page/per-page selection. One call returns one page; it does not inject a default tenant or claim a cache-freshness guarantee.
  • Snapshot inputs and error settings before asynchronous work. Invalid input and direct OPA mode reject before HTTP; malformed successful responses and unavailable endpoints remain visible even when ordinary operational errors use the documented empty-array fallback.
  • Add strict CommonJS/ESM consumers, request/response and privacy regressions, public reference and README guidance, and owned local filter/pagination/removal acceptance. Preserve all existing source-operation inventory and evidence guards.

Proxy configuration APIs (PER-16947)

  • Add permit.api.proxyConfigs.list/get/create/update/delete with the complete published configuration, secret and mapping-rule types. Use selected-environment control-plane routing, with page 1 and perPage 100 defaults.
  • Snapshot write bodies before asynchronous initialization. Preserve complete received secrets or masks without restoring submitted plaintext; successful responses retain all published fields.
  • Extend bounded error redaction to response-only secrets before projecting error metadata, causes and logs. Reused errors retain privacy across SDK entries; accessor-bearing, sparse or nonstandard response arrays fail closed without invoking getters or changing caller objects.
  • Add strict installed CommonJS/ESM consumers, behavioral and negative-path tests, public reference documentation and owned local lifecycle/readback/cleanup acceptance. Public source is independently authored from public contracts.

API-key management (PER-16949)

  • Add permit.api.apiKeys.list/get/create/delete/rotate/getScope using the published control-plane routes. Server authorization governs access; management calls neither initialize nor inject the selected API context.
  • Preserve complete records, pagination envelopes, optional secrets and additive response fields. Scope IDs, optional read metadata/secrets and page counts accept verified null values; creation inputs and required output fields remain unchanged. Checked generation corrections cite the public Python SDK and reject source-schema drift.
  • Snapshot creation bodies before dispatch. Rotation uses an unreplayed bodyless POST, does not replace the client's credential, and makes no stable-ID or always-present-secret promise.
  • Document list defaults and filters, scope discovery and secure handling of returned credentials. Named REST errors retain sanitized diagnostics while successful results remain complete.
  • Extend the public inventory to 176 methods, retaining all 307 source operations, 266 generated methods and 408 generated models. Strict TypeScript 6/7 consumers and both package entries cover the exposed contracts.

Reference validation and CodeQL follow-up (PER-16958)

  • Check out GitHub's immutable event SHA for reference builds after validating the selected main commit. Preserve the existing approval, source identity and deployment guards; remove the unused dispatch-derived checkout output.
  • Decode supported HTML entities once when checking reference links. Nested entity text remains literal, so a second decoding pass cannot accept the wrong file or reject the intended target.
  • Name the synthetic cookie fixture as a redaction canary. Its bytes and leakage assertions are unchanged; independent mutation checks still fail if cookie values enter diagnostics. No security rule or alert is disabled.

Authorization audit logs (PER-16950)

  • Add permit.api.auditLogs.list/get for the published control-plane read routes. Preserve complete page/detail results, raw engine branches and additive response data.
  • Snapshot the twelve published filters before asynchronous context discovery. Preserve repeated array entries and explicit false, zero and empty values; selected environment scope cannot be replaced by extra caller properties.
  • Correct 71 optional read fields across 16 schemas using pinned public Python SDK evidence. Keep requiredness, request inputs, the captured OpenAPI snapshot and unrelated schemas unchanged. Reject new direct or transitive request use, including supplemental schemas, before applying the correction.
  • Export the public interfaces and generated contracts, document pagination and filter behavior, and regenerate the reference. Error diagnostics omit received audit contents while successful results remain complete.
  • Cover strict installed CommonJS/ESM consumers, malformed responses and failures, filter transport, full envelopes, nullable outputs and privacy. Public implementation and schema evidence come from public contracts; private infrastructure and raw test evidence remain outside this repository.

Asynchronous environment copy (PER-16951)

  • Add permit.api.environments.copyAsync/getCopyResult for explicit submission and result retrieval. Preserve the complete task envelope, statuses, nullable results/errors and additive received fields; the SDK does not poll automatically.
  • Support the published optional wait value, including zero and fractions, with finite 0–60 validation before dispatch. Snapshot copy bodies before asynchronous initialization and preserve explicit source scope and encoded identifiers.
  • Correct only the task result/error output nullability using pinned public contract evidence. Preserve request types, required properties and the captured OpenAPI source; checked generation rejects schema drift.
  • Extend README guidance, generated reference, strict installed CommonJS/ESM consumers and API/evidence catalogs. The inventory now covers 180 public methods and all 307 source operations.
  • Validate actual new-target and existing-target copies, scoped policy selection, overwrite behavior, terminal result readback, unchanged source/other-target policy and verified cleanup. Public implementation derives from public contracts; private code and raw runtime evidence stay outside this repository.

Node release acceptance (PER-16573)

  • Replace the release evidence format with one strict schema. Compute Node publication readiness from exact source/package/consumer-lock identities, feature-specific proof, complete runtime/phase/case coverage, cleanup and existing quality/security/migration gates. Missing local execution stays visible as incomplete; producer labels and unsigned hashes do not establish runtime proof.
  • Add public-contract cloud producers for check, bulkCheck, getAuthorizedUsers and getUserPermissions through both installed package entries on all five Node runtimes. Require allow/deny convergence, full response oracles, complete observations and actual required-header presence.
  • Use trusted pre-checkout setup and verified environment credentials in each candidate job, masked through same-job step outputs. Keep broader project credentials outside candidate/dependency execution, with separate always-cleanup and finite noncredential completion receipts. This uses step separation, not separate-runner isolation.
  • Require captured child inventories and immutable parent identity before deleting owned cloud environments. Unknown writes, replacements, changed policies and partial/error inventories retain state and receive no false cleanup credit. With the project-level CI key, API keys are checked through the environment's own key, which must match its single PDP configuration; additional environment keys cannot be enumerated and would be removed with the environment.
  • Supply a UUID for each logical PDP request when no usable inherited request ID exists, preserve usable IDs and other headers, and keep the ID stable through SDK retries. OPA/control-plane behavior and the public configuration remain unchanged.
  • Match the cloud proof to the managed cloud PDP as observed in CI: wait for a fresh environment's first check to become ready (timeouts, network errors and 404, 429 or 5xx only, bounded and counted as readinessFailures), expect tenant-level grant roles as __tenant#<role>, and accept user-permissions roles as the bare key, the qualified key, both, or one of them with the built-in tenant-association role. Closure capture accepts the environment's one to four PDP configurations. Cloud lifecycle and proof failures print one static diagnostic line, and a refused capture prints per-kind row counts; neither prints identifiers, names or response text.
  • Test the publication readiness checker the way the publish workflow runs it, assert exact diagnostics for every negative evidence case, cover closure page shapes and disappeared captured rows, and check X-Request-ID behaviour of both built package entries against a loopback PDP.
  • Retain owner-deferred Curtain Call (PER-16574) and bulk counts (PER-9298), the unavailable shared five-SDK target (PER-16345), required backend rollout (PER-16954) and the pending exact local-evidence handoff. Publication readiness remains false until every required proof is available.

Verification

  • SDK head 7251192b8507d93e40ea32aed4770b640a1486ef, tree 95475e2ecbc0623a5853738a9715a5619b4a4f0e: ten PER-16573 commits on top of 097f8e37. The first reproduces the reviewed tree ac2b8fc5 plus a refreshed PDP resolution date (same digest) and an Oxfmt-only fix to api-coverage/baseline.json; the second adds tests closing 21 surviving mutants; the rest adapt the cloud lifecycle and proof to the real backend and managed cloud PDP, one observed difference at a time.
  • Canonical verification on the final tree: pnpm verify passes hooks, both builds, 3,217 tests across 122 files (unit 1,512, module imports 54, codegen 1,651) and API contracts. The public reference check passes 266 pages and 24,435 local references; check:openapi passes two clean generations.
  • The packed archive has 453 files and SHA-256 d9eaf51771e26022b7495519ab91843e2637510d0b4fd67a731f1ad5c2cd296a; no commit after the first changes it. CI built the same bytes from merge commit 7269b5ee, whose tree equals the tested tree. An external consumer installs the archive from the reviewed lock with only the candidate integrity changed; the audit finds no known vulnerabilities and lifecycle scripts stay disabled.
  • Isolated local runs on the final commit pass on Node 22.13.0, 22.23.3, 24.0.0, 24.21.0 and 26.11.0. Each passes 166 phases and 23,679 assertions, all 419 local catalog cases (including persisted inline roles, async copy to new and existing targets, API keys and audit-log ingestion), six released-2.7.5 comparisons and all 141 candidate cleanup records. An earlier attempt failed one async-copy phase on Node 22.13.0 because of a timing race in the private test harness; it is kept on record, and the harness fix passed all five runtimes before the final runs.
  • Hosted checks: all 36 checks pass on 7251192b, including CodeQL and run 38039730774. On every Node version the packed CommonJS and ESM entries pass check, bulkCheck, getAuthorizedUsers and getUserPermissions against the managed cloud PDP with eight observed HTTPS requests, all carrying X-Request-ID, and the owned cloud environment is deleted and verified absent. Eight earlier runs on this branch failed while the cloud adaptations above were found; their failures are kept in CI history. Three of them (38024605946, 38027377392, 38030263225) retained their owned cloud environments by design.
  • Independent reviews approved every commit's exact tree: the release batch and formatting fix, the adopted tests (the test review's 83-mutant sweep now leaves only 9 with no observable effect), and each cloud lifecycle and proof change, including leak checks of every diagnostic code.
  • Computed publication acceptance: the hosted cells and the final local rows compose into one schema-2 bundle (15 runs). Without trusted CI identity on a local machine, the validator refuses to credit hosted cells, which is its designed behaviour, and reports no failures. Full acceptance requires the owner-approved local-evidence handoff into trusted CI and the PER-16954 rollout; publication readiness stays false.

Remaining validation

  • Complete the exact owner-approved local-evidence handoff and required backend rollout tracked by PER-16954. Neither is authorized or completed by this PR description. User-attribute PATCH local evidence uses the separately reviewed backend fix; it does not prove deployed behavior.
  • The shared five-SDK target remains UNAVAILABLE (PER-16345). Local execution does not establish cross-SDK parity. Curtain Call (PER-16574) and useful bulk-response counts (PER-9298) remain owner-deferred; the nine empty bulk response contracts remain truthful.
  • The earlier bulk-role startup incident tracked by PER-16553 and the explicit ABAC coverage marker retain their recorded status. Later URL-authorization success does not close that separate incident. Optional organization/project-scope tests remain explicitly unavailable without their credentials; mandatory environment-key coverage must execute.
  • Required-check/ruleset settings and production/tag/Trusted Publisher owner actions remain outstanding. Hosted-reference publication requires its separate owner approval, Pages settings and source-marker verification. No merge, tag, npm publication or website deployment is claimed.
  • Human review continues to request changes until its review decision is updated. The human changes-requested review is still in place; the green checks are not human approval.

Credits

@Kyzgor contributed #122's native URL implementation and equivalence tests; the branch preserves that authorship. This PR also incorporates #131, #132 and #133.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VBeEwQ8PgCWeTywudJezCG

Kyzgor and others added 6 commits June 23, 2026 22:48
url-parse was used only to build the OPA client base URL. Node's native
WHATWG URL (available since v10; engines.node already requires >=10) does
the same, so extract a buildOpaBaseUrl() helper and drop url-parse and
@types/url-parse.

yarn.lock is pruned of url-parse and its now-orphaned transitive
dependencies (querystringify, requires-port) only; every other entry is
left byte-for-byte unchanged.
Lock the exact OPA base URL produced for the default PDP, trailing-slash,
explicit-port, https, and path-prefix inputs so the url-parse -> native URL
refactor is proven behaviour-equivalent on valid input and any regression
fails here; assert a scheme-less PDP (bare host or //host:port) throws; and
assert the Enforcer wires the OPA client baseURL to buildOpaBaseUrl(pdp).
Pin actions/checkout (v7.0.0) and actions/setup-node (v6.4.0) to full
commit SHAs in both workflows, set persist-credentials: false on all
checkouts, and bump the CI node matrix from 18/20 to 20/22 (18 is EOL).

Run the full suite on PRs/pushes, not only on release. Same-repo events
provision a throwaway Permit env via PROJECT_API_KEY, run a dockerized
PDP (now with -e PDP_API_KEY/PERMIT_API_KEY and a /healthy readiness
wait), execute test:ci:full, and delete the env on always(). Fork and
secret-less runs fall back to the no-backend test:ci:unit suite. Add the
two supporting scripts and quote $GITHUB_ENV in the publish workflow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace AVA 3 with Vitest 4.1 (vitest.config.ts with unit / module-imports
/ integration / e2e projects; backend projects run serially via forks +
maxWorkers=1 to avoid shared-env collisions). Keep test:ci:unit /
test:ci:full names so the CI workflow is unchanged.

Remove every timer-based propagation wait: a new waitFor/waitForCheck
helper polls the actual permit.check() until it converges, bounded by a
timeout, replacing the fixed sleep(10s) waits in the e2e suites.

Rewrite fixtures to a createTestClient() factory (handleApiError now
throws). Migrate all t.* assertions to expect. Module-import specs load
the built bundle (build/index.{js,mjs}) to keep packaging-regression
coverage. Wire in the two previously orphaned specs (bulk, lists) with
proper setup/cleanup; preserve bulkRelationshipTuples coverage. Keep the
inherently racy local_facts "skip wait" case as it.skip and add a
deterministic waitForSync header unit test. Drop ava/nyc/codecov/ts-node;
add vitest/@vitest/coverage-v8; bump @types/node to ^20; skipLibCheck for
Vitest's d.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a shared mock seam (src/tests/helpers/mock-api.ts, createMockPermit)
that patches the axios adapter on the REST, PDP and OPA transports and
seeds API context without network, then add unit specs covering every
API module (resources, roles, resource-roles, role-assignments, users,
tenants, resource-instances, resource-relations, relationship-tuples,
condition-sets, condition-set-rules, resource-actions/attributes/
action-groups, projects, environments, elements, deprecated), the
enforcer (check/bulkCheck/getUserPermissions/checkAllTenants, string
parsing, default-tenant, OPA path, response shaping, throwOnError) and
the utils/config layer. Add one ABAC e2e (condition-sets) following the
event-based, self-cleaning conventions.

262 new unit tests; full no-backend suite is 333 tests. Tests-only; no
SDK source changes. Tests assert current behavior of two latent bugs
(checkAllTenants payload PER-15318; unreachable PermitPDPStatusError),
flagged in-code, not fixed here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
checkAllTenants passed { headers, params } as the axios POST body (2nd
arg), so the Authorization header was never sent and the query was
nested under `params` instead of being the request body — the PDP could
neither authenticate nor read the request.

Mirror check(): send the normalized { user, action, resource, context }
as the body and pass headers/timeout as the axios config arg. Normalize
the string forms of user/resource but skip default-tenant injection,
since an all-tenants query must not be pinned to a tenant. Add an AVA
regression test asserting the auth header is sent, the body shape is
correct, and no tenant is injected.

Fixes PER-15318

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zeevmoney and others added 23 commits June 28, 2026 23:40
The e2e suites are AVA with fixed sleep(10s) waits and fail fast on the
first error. Against a freshly started PDP they hit a momentary
ECONNREFUSED window right after the write burst (OPA reload), which kills
the whole run even though the env, key, and policy sync are all healthy
(/healthy passes). Scope the PR backend run to the suite that reliably
passes — unit + integration + module-imports (what `yarn test` runs, the
same set the publish workflow runs). The event-based, error-tolerant e2e
lands in the stacked test-migration PR, which re-includes e2e in CI.

Also add a PDP diagnostics step (docker logs + container state +
/healthy) on backend-run failure so PDP connection errors, which surface
with no HTTP response, are debuggable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…test-event-based-tests

* per-15306/ci-pin-actions-tests-on-pr:
  ci: run unit/integration/module-imports on PR, defer e2e to next PR

# Conflicts:
#	package.json
…rehensive-sdk-tests

* per-15315/vitest-event-based-tests:
  ci: run unit/integration/module-imports on PR, defer e2e to next PR
Stacked PRs target feature branches, so a pull_request filter of
branches:[main] meant they never ran CI. Drop the base-branch filter so
every PR is tested regardless of base.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…test-event-based-tests

* per-15306/ci-pin-actions-tests-on-pr:
  ci: run on all pull requests, not only those targeting main
…rehensive-sdk-tests

* per-15315/vitest-event-based-tests:
  ci: run on all pull requests, not only those targeting main
Node resolves `localhost` to ::1 (IPv6) first, but the GitHub runner's
Docker IPv6 port publish refuses connections, so e2e permit.check() calls
hit ECONNREFUSED even though the PDP is healthy on IPv4 (curl /healthy
returns 200). Set PDP_URL to http://127.0.0.1:7766 so the SDK uses the
working IPv4 path, and pin the readiness probe to 127.0.0.1 too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…test-event-based-tests

* per-15306/ci-pin-actions-tests-on-pr:
  ci: pin PDP connection to IPv4 (127.0.0.1) in the backend test run
…rehensive-sdk-tests

* per-15315/vitest-event-based-tests:
  ci: pin PDP connection to IPv4 (127.0.0.1) in the backend test run
The dockerized PDP in CI doesn't expose OPA (port 8181), so rbac's direct
useOpa checks hit ECONNREFUSED. Gate them behind PERMIT_RUN_OPA_E2E
(default off) so they only run against an OPA-exposed setup. Raise the
rebac convergence gate to 150s and the e2e test timeout to 300s, since
the heavy ReBAC graph needs longer to propagate cloud->PDP on a cold env.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rehensive-sdk-tests

* per-15315/vitest-event-based-tests:
  test: make rbac useOpa checks opt-in and widen rebac CI budget
bulkCheck and getUserPermissions query separate PDP endpoints that can
lag a single permit.check, so the direct assertions raced cloud->PDP
propagation and flaked on the slower matrix leg. Gate the complete-user
read and poll bulkCheck/getUserPermissions until they converge before
asserting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rehensive-sdk-tests

* per-15315/vitest-event-based-tests:
  test: poll the rbac multi-result reads to remove propagation races
A userset condition set referencing user.<attr> requires that attribute
to exist on the built-in user resource; users.sync alone doesn't register
it, so the condition-set creation failed with 400 MISSING_RESOURCE_ATTRIBUTE.
Register a run-unique attribute on the __user resource before creating the
userset, reference it consistently in the condition and the synced users,
and remove it in the tolerant afterAll.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Condition sets compile to new policy (rego), which propagates slower than
role/fact writes, so the 60s default left the ABAC check timing out in CI
before the policy took effect. Match the heavier rebac budget.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PER-15318

Read allowed_tenants and return the tenant details. Merge the global
context store into the request context, with caller keys taking
precedence, as check() does. Replace adapter fixtures with real local
HTTP tests for normalized POST bodies, authentication, SDK-language
headers, attributes, empty decisions, and global context merging.

Add a shared local PDP test server for these and later regression
tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PER-16492

Merge each check's context over the method context before deriving the
global context. Keep sibling checks and caller-owned contexts isolated.

Add local HTTP regression coverage for precedence, optional method
context, shallow merging, and unchanged inputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PER-16494

Raise PermitPDPStatusError with statusCode and responseBody for HTTP
responses, including Axios rejections, in check, bulkCheck,
getUserPermissions and checkAllTenants. Preserve transport connection
errors.

HTTP error responses that Axios rejects, such as 401 and 500, used to
raise PermitConnectionError with a connection-failure message. Their
error name is now PermitPDPStatusError, and their message is the one
used for unexpected resolved statuses: "Permit.<method>() got an
unexpected status code: <status>, ...". The message does not include
the user, action or resource.

A 200 response with a body the SDK cannot read, such as {}, also used
to raise PermitConnectionError saying the SDK cannot connect to the
PDP. It now raises PermitPDPStatusError with statusCode 200, the raw
body in responseBody, and a message saying the PDP returned an
unexpected response body.

Make PermitPDPStatusError extend PermitConnectionError so existing
instanceof PermitConnectionError catches keep handling HTTP failures
that previously surfaced as connection errors. Keep one-argument
construction available; SDK-generated HTTP errors fill both new fields.

checkAllTenants no longer rethrows the raw AxiosError, which carried
the request config and its Authorization header. It maps PDP errors
like the other methods and, when throwing, logs each one once, without
the extra log in Permit.checkAllTenants. Like check and bulkCheck, it
applies the SDK throwOnError setting to every failure, including an
invalid resource string: with throwing disabled it logs the error and
returns an empty tenant list. With throwing disabled, bulkCheck
returns one false per input check instead of an empty array.

Cover 401, 500, unexpected resolved statuses, string response bodies,
unreadable 200 bodies, and transport timeouts for all four methods,
and invalid resource strings for the three methods that take a
resource, as separate tests, with per-call and global error-policy
overrides. Check that thrown errors contain neither the API key nor
user details.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PER-16493

Stop serializing SDK configuration in debug logs. Use plain Pino JSON
for JSON mode. For pretty mode, write through a synchronous in-process
pino-pretty stream instead of a Pino transport, so bundled apps need no
worker-thread target and Permit instances add no process exit
listeners.

Keep JSON lines as the default output: log.json defaults to true. When
log.json is omitted, PERMIT_LOG_JSON=false selects pretty output. The
variable ignores letter case and surrounding whitespace, and any other
value keeps JSON lines instead of making new Permit() throw. An
explicit log.json always overrides the environment variable.

Cover default, explicit and environment JSON and pretty settings with
12 instances each, PERMIT_LOG_JSON values and overrides, configured
secret exclusion, and debug logging for successful calls, HTTP errors
and connections the PDP closes without replying, for all four PDP
methods, with throwing enabled and disabled. HTTP errors must surface
as PermitPDPStatusError and closed connections as
PermitConnectionError, and in JSON mode a thrown failure must produce
exactly one error log record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PER-16493
PER-16494

Add a Logging and errors section to the README. Describe log.level,
the JSON default, how PERMIT_LOG_JSON is read, pretty output, and how
an explicit log.json overrides the environment variable. Describe
PermitPDPStatusError and PermitConnectionError, including unreadable
200 responses, the statusCode field and the raw responseBody, matching
errors with instanceof, and what each PDP method returns when
throwOnError is false.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove unused imports and constants from the e2e and module-import
specs, and replace a non-null assertion with an equivalent type
assertion. These warnings are pre-existing on main. There is no
behaviour change: the emitted JavaScript differs only by two removed
unused constants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PER-16544

PermitApiError stored the raw AxiosError in its enumerable
originalError field. The error's request config carried the
Authorization header with the API key, and its Node request object
carried the same header in its raw header block, so logging a failed
REST call with util.inspect, JSON.stringify, pino's err serializer or
an error tracker leaked the key. The deprecated permit.api methods
rethrew the raw AxiosError, with the same exposure.

Remove credentials from the Axios error before it is thrown. Reduce
the request config to method, URL, params, body and timeout, redact
the value of every request header except a short list that carries no
credentials, redact the response Set-Cookie header, and drop the
request objects. The status, response body, method and URL stay
available for debugging. PermitApiError.request is now undefined.

Cover 401 and 500 responses from a current and a deprecated REST
method, and a connection reset, against a local server. Check that
util.inspect, JSON.stringify and pino output contain neither the API
key, a custom header secret nor a cookie, and that the useful fields
remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PER-15306
PER-15315
PER-15317

Fold the stacked test and CI branches into this branch so the SDK
fixes land together with the Vitest suite, the per-module unit tests
and the CI changes. The merged head is #133 (a71e7ad), which contains
#132 (64b288c) and #131 (fc2529b).

Conflicts:
- src/tests/e2e/lists.e2e.spec.ts, src/tests/e2e/rbac.e2e.spec.ts and
  src/tests/module-imports/esm-import.spec.ts: this branch only removed
  unused imports from the AVA versions. The stack rewrote these files
  for Vitest, so the stack's versions are kept.
- src/tests/unit/config.spec.ts: both sides added the file. The stack's
  Vitest version is kept here; the next commit ports this branch's
  PERMIT_LOG_JSON cases into it.

The SDK sources are this branch's; the stack did not touch them. The
stack's package.json replaces AVA, nyc, ts-node, codecov and open-cli
with Vitest, so this merge changes the dev dependencies and the lock
file. This branch's AVA unit specs are ported to Vitest in the next
commit, and the stack's tests that pin the old SDK behaviour are
updated after that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The semver-major change spans generated contracts, runtime behavior, packaging, CI, and security gates while release evidence still reports 12 unproved operations and pending CI.

Review effort: Balanced
Findings: 1 High severity

Open (1)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The release-wide API and generated-contract changes are extensive, while the supplied release evidence remains invalid and new-head CI is still pending.

Review effort: Balanced
Findings: 1 High severity

Open (1)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The major release spans generated contracts, public APIs, build tooling, dependency security, and release validation, requiring final human review.

Review effort: Balanced
Findings: 1 High severity

Open (1)

Comment thread .github/workflows/reference-pages.yaml Fixed
Comment thread .github/workflows/reference-pages.yaml Fixed
Comment thread .github/workflows/reference-pages.yaml Fixed
Comment thread scripts/public-reference.mjs Fixed
Comment thread src/tests/unit/api/api-errors.spec.ts Fixed
zeevmoney and others added 13 commits October 8, 2026 11:27
- Replace release evidence with a strict schema-2 format and computed
  Node acceptance that binds source, package, baseline, consumer locks,
  cases, phases, cleanup and quality gates. Publication stays false
  while required proof is missing.
- Add trusted-CI cloud proof for check, bulkCheck, getAuthorizedUsers
  and getUserPermissions through the CJS and ESM entries on all five
  supported Node versions, with ownership and closure checks before
  cloud fixture cleanup.
- Send a default X-Request-ID on PDP requests: keep a usable inherited
  ID, otherwise generate a UUID that stays stable across retries. OPA
  and control-plane requests are unchanged.
- Refresh the Enforcer class and constructor baseline fingerprints,
  format api-coverage/baseline.json, and record the 2026-10-10 PDP
  image resolution.
- Update the README, contributor guide, evidence docs and generated
  reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBeEwQ8PgCWeTywudJezCG
- Run the publication readiness checker as the publish workflow does
  and assert it refuses, and that the workflow job runs it.
- Assert exact diagnostics in negative evidence tests, and fix rows
  that passed through an unrelated check: consumer lock, cleanup
  reconciliation, empty PASS phases, absent gate results and
  unreviewed PDP digests.
- Make two release-gate tests read the inputs they set up.
- Cover closure page-reader shapes, disappeared captured rows and the
  unknown-write and active guards.
- Check X-Request-ID behaviour of both built package entries against
  a loopback PDP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBeEwQ8PgCWeTywudJezCG
The first CI cloud run refused the closure inventory of its own fresh
environment. Checked against a real backend:

- page_count can equal total_count, so the page reader no longer
  compares it with ceil(total_count / 100). Consistent totals,
  duplicate refusal and the final row count still prove completeness.
- Listing environment API keys needs an organization key. Closure
  capture, settle and verify now read the environment's own key with
  the project key and bind it to the PDP config secret. The docs state
  that additional environment keys are not enumerated.
- Fresh environments include the __role built-in resource and a
  tenant-association role on __tenant; both are allowed and stay in
  the recorded baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBeEwQ8PgCWeTywudJezCG
Cloud setup and cleanup failures printed one constant message, so CI
could not show which check refused a production response. Each refusal
now carries a static code built only from code constants (area, rule,
inventory kind and HTTP status). Setup and cleanup print one
"Cloud lifecycle diagnostic: <code>" line before the unchanged error.
Codes that do not match the allowlist pattern, or that contain an
ID-shaped hex run, print "unclassified". Exit codes and cleanup credit
are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBeEwQ8PgCWeTywudJezCG
CI showed that a fresh production environment returns more than one
PDP configuration, while closure capture allowed at most one. Capture
now accepts one to four in-scope configurations and requires the
environment key to match exactly one of them; settle and verify still
refuse any later addition, removal or change.

When capture refuses after reading the inventory, setup also prints a
census line with the row count of each static inventory kind, so the
next refusal shows every default count at once. Counts are bounded
integers and the line must match a strict pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBeEwQ8PgCWeTywudJezCG
All five packed cloud SDK jobs stopped about ten seconds into the first
check against a freshly created environment, and the CLI printed only a
constant message.

- While the first check waits for the new environment, timeouts,
  transient network errors and 404, 429 or 5xx responses count as not
  ready within the existing 30 bounded attempts. 400, 401 and 403 still
  fail at once, and every later request must return 200.
- Each HTTP observation records readinessFailures (0 to 29, nonzero
  only for cloud.check); the release evidence validator enforces it and
  request-ID presence still covers every request.
- The CLI prints one "Cloud proof diagnostic: <code>" line made of
  static case, entry, failure kind and status tokens before the
  unchanged error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBeEwQ8PgCWeTywudJezCG
The managed cloud PDP returned an authorized-users grant that failed
the combined grant oracle, and the diagnostic named only the oracle
index. When an oracle fails, the diagnostic now adds a short summary
of how each compared field differs, using fixed class tokens computed
by comparing the received value with values derived from the owned
fixture (for example ok, absent, bare, prefixed or other). Received
text is never printed and every oracle condition is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBeEwQ8PgCWeTywudJezCG
The managed cloud PDP reported the authorized-users grant role as a
qualified identifier ending with the role key, where the container PDP
returns the bare key. The cloud proof now requires exactly
__tenant#<role> for that grant and accepts it as the only additional
form in user-permissions roles. Role mismatch classes distinguish the
bare key and other qualified forms so a different format is named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBeEwQ8PgCWeTywudJezCG
With the qualified grant role confirmed, the managed cloud PDP's
user-permissions response listed an accepted role together with
another entry. The roles oracle now accepts exactly the bare key, the
tenant-qualified key, or both once each in either order. Other role
lists are refused with distinct classes for duplicates and one or more
unknown entries. The release evidence notes describe both forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBeEwQ8PgCWeTywudJezCG
The managed cloud PDP's user-permissions roles listed one accepted role
form together with an entry unrelated to the fixture role. Fresh
environments carry the built-in tenant-association role on __tenant,
so the roles oracle now also accepts exactly one role form plus one
tenant-association form, bare or tenant-qualified, in either order.
Unrecognized entries are classified as association, other qualified
names or other, so a different shape is named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBeEwQ8PgCWeTywudJezCG

This branch has not been deployed

No deployments
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.

url-parser is an unnecessary dependency

5 participants