Task Breakdown: Ever ID
Ordered tasks derived from
plan.mdandcross-platform.md. Each is small enough to land in one PR and ships with tests per Constitution VI. Schema tasks ship their migration in the same PR per Constitution V.
Epic ID: APW-12-ever-id
Spec: ./spec.md · Plan: ./plan.md
Status: Draft
Last updated: 2026-09-17
How to use
- Tasks are sequential by default.
(parallel)means it may run alongside its predecessor. - Every task names the exact files to create or modify. Paths in this monorepo marked new do not exist yet;
every other monorepo path was checked with
git ls-filesondevelop@ee45946e5. Paths in other repositories are relative to that repository's root, were read ondevelopthrough the GitHub contents API on 2026-09-17, and are re-opened before the task starts (cross-platform.mdheader). - Every task has a Test line (file + assertion, or the run command for a test task) and a Done when line
that is checkable without reading the diff and names the acceptance IDs it proves (
ACC-12-xx,XP-T-xx,XP-G-xx). - Phase boundaries are ship boundaries:
developis green and deployable at the end of each phase. - Add new tasks at the bottom rather than renumbering. Migrations are authored from
apps/api/. - Live changes (enabling a plugin or flag on stage or production, registering clients at the provider) follow the operations change process in the private operations repository; nothing in this file authorises one by itself.
- Program audit resolutions (CONTRACTS.md §0) applied here: R-1 (T3), R-2 (T15, T45), R-19 (T16,
T18), R-22 (T20 — no suite under
apps/api/test/). - Run commands (this monorepo, from the root):
- contracts:
pnpm --filter @ever-works/contracts test; plugin contracts:pnpm --filter @ever-works/plugin test - identity plugin:
pnpm --filter @ever-works/oidc-identity test; agent:pnpm --filter @ever-works/agent test -- external-identity - API:
cd apps/api && pnpm test -- ever-id - web unit:
pnpm --filter ever-works-web test -- ever-id; web e2e:pnpm --filter ever-works-web test:e2e ever-id - CLI:
pnpm --filter ever-works-cli test; node:pnpm --filter ever-works-node test
- contracts:
- Run commands (other repositories): Ever Teams
yarn test:web(Jest) andyarn e2e:web(Cypress); Ever Gauzyyarn nx test core,yarn nx test auth,yarn nx test common,yarn nx test ui-auth,yarn nx test mcp-auth(Jest) andyarn nx e2e gauzy-e2e(Playwright).
Phase P0 — Decision gate (no product code)
-
T1. Owner decisions. Modify
docs/specs/features/app-works/APW-12-ever-id/idp-options.md§6 — record D1–D9 (answer, date, decider) and setStatus: Decided. Modifydocs/specs/features/app-works/README.md§8 question 6 with the answer, anddocs/specs/features/app-works/TRACKER.mdnotes for APW-12. Test: review check in the PR — every D-row ofidp-options.md§6 carries an answer, a date and a decider andStatusreadsDecided;npx prettier --checkpasses on the three files. Done when: every D-row has an answer and none is left at its default silently. Owner answers already recorded (2026-09-17): D1 is answered — the provider is ZITADEL, self-hosted as-is — and D2 is answered — the public domain isauth.ever.co, verified free in the liveever.cozone, one instance serving every platform. Both are quoted inspec.md§9 and inidp-options.md§6. T1's remaining scope is therefore D3–D9; it does not re-open D1 or D2, and D1 is not a gate on any later task. -
T2. Provider stood up (private operations repository). (operator attestation) Three environments, at least 3 replicas each, verified database backups, and the clients, scopes, audiences, lifetimes and back-channel logout URIs of
idp-options.md§6.1. No hostname or address is written into this repository. Modify nothing in this repository: the provider configuration and its change record are created in the private operations repository;docs/specs/features/app-works/TRACKER.mdgets only the P0 tick (T1). Test: the Ever Works administrator Test connection screen (T6) against the development provider returns every FR-3 check green; the result is recorded in the private operations change log asapw12-provider-standup. That run needs P1 code, so it is driven by T47 (P1, after T6) and is not a condition of closing T2. Done when: the development discovery document satisfies every row of plan §4.3 by manual inspection — issuer, endpoints,code_challenge_methods_supportedcontainingS256, the signing algorithms, the back-channel logout support and the device authorization endpoint — recorded in the operations change log; T47 records the same provider passing the in-product Test connection once P1 ships.
Phase P1 — Ever Works relying party
Delivers spec FR-1…FR-53 and ACC-12-01…ACC-12-39.
P1.1 — Contracts
-
T3. Shared Ever ID types. Create
packages/contracts/src/apps/ever-id.ts(new, Resolution R-1) exactly as plan §3.5. Modify APW-03's barrelpackages/contracts/src/apps/index.ts—export * from './ever-id.js';(create the barrel, andexport * from './apps/index.js';inpackages/contracts/src/index.ts, only if APW-03 has not landed). Test:packages/contracts/src/apps/__tests__/ever-id.spec.ts(new) pins everyEVER_ID_LIMITSnumber, both scope strings, the algorithm list and the error-code union; runpnpm --filter @ever-works/contracts test. Done when:pnpm --filter @ever-works/contracts buildemits declarations,apps/apiimportsEVER_ID_LIMITSfrom@ever-works/contracts, andpackages/contracts/src/__tests__/index.barrel.spec.tspasses. -
T4 (parallel with T3). Identity provider capability contract. Create
packages/plugin/src/contracts/capabilities/identity-provider.interface.ts(new, plan §4.1) and export it frompackages/plugin/src/contracts/capabilities/index.ts. Modifypackages/plugin/src/contracts/facade-capabilities.ts(IDENTITY_PROVIDER: 'identity-provider') andpackages/plugin/src/contracts/plugin-manifest.types.ts(append'identity'toPLUGIN_CATEGORIES). Modifyapps/web/src/lib/utils/plugin-category-icons.ts— its two exhaustiveRecord<PluginCategory, …>maps (icon and label) stop compiling the moment'identity'joins the union, so the new category needs an icon, a label and any label i18n the file reads; the file's own assertion that every category has both stays as it is. Test:packages/plugin/src/contracts/__tests__/identity-provider.interface.spec.ts(new) — the type guard, the closedIdentityTokenRejectedErrorcode set, the category present; runpnpm --filter @ever-works/plugin test; extendapps/web/src/lib/utils/plugin-category-icons.unit.spec.ts(or the file's existing spec) with theidentitycase. Done when: the plugin package builds, the rootpnpm type-checkis green (the category maps are exhaustive, so a missing icon or label fails the build rather than at runtime), and no existing category or capability changed.
P1.2 — The oidc-identity plugin
-
T5. Scaffold and settings schema. Create
packages/plugins/oidc-identity/(new:package.jsonnamed@ever-works/oidc-identitywith theeverworks.pluginblock of plan §4.2,tsup.config.ts,vitest.config.ts,src/index.ts,src/settings.schema.ts,src/oidc-identity.plugin.tsskeleton). Dependenciesopenid-client@^6,jose@^6in this package only (the built-in plugin list is updated in T33). Test:packages/plugins/oidc-identity/src/__tests__/settings.schema.spec.ts(new) —clientSecretisx-secret; every key isx-scope: 'global'; limits (1–3 issuers, ≤ 5 local clients, skew 0–120,httponly for localhost outside production). Done when:pnpm --filter @ever-works/oidc-identity build testis green and plugin discovery lists it disabled by default. -
T6. Discovery, key cache and Test connection. Create
packages/plugins/oidc-identity/src/discovery.ts,src/jwks-cache.ts(new); implementtestConnectionandgetPublicConfiginsrc/oidc-identity.plugin.ts. Test:packages/plugins/oidc-identity/src/__tests__/test-connection.spec.ts(new) — each FR-3 check within 5 s and the secret absent from the output (ACC-12-03);src/__tests__/jwks-cache.spec.ts(new) — 600 s cache, 30 s unknown-key cooldown, 21,600 s staleness then fail closed, a rotated key validates after one refresh and a removed key is refused after the next (ACC-12-08), 5 s timeout, one retry. Done when: both specs pass and no output contains the secret (ACC-12-03, ACC-12-08). -
T7. Authorization request, code exchange, ID token validation. Modify
packages/plugins/oidc-identity/src/oidc-identity.plugin.ts— implementbuildAuthorizationRequestandexchangeAuthorizationCode; createpackages/plugins/oidc-identity/src/scopes.ts(new, sign-in scopesopenid email profile, neveroffline_access). Test:packages/plugins/oidc-identity/src/__tests__/authorization-request.spec.ts(new) — S256, fresh 32-bytestateandnonce, exact redirect (ACC-12-06);src/__tests__/id-token.spec.ts(new) — every FR-11/FR-12 rejection and the ±60 s skew edges (ACC-12-07). Done when: ACC-12-06 and ACC-12-07 are proven at unit level. -
T8. Access token, logout token, end-session URL, fake provider. Implement
verifyAccessToken,verifyLogoutToken,buildEndSessionUrl. Createpackages/plugins/oidc-identity/src/testing/fake-oidc-provider.ts(new) exported only through a./testingsubpath (plan §10.4). Test:packages/plugins/oidc-identity/src/__tests__/access-token.spec.ts(new) — lifetime > 3,600 s and a wrong audience refused (ACC-12-35), unlistedazp, missing scope,iat> 300 s refused (ACC-12-29 unit half);src/__tests__/logout-token.spec.ts(new) — reusedjti, anonce,iatolder than 300 s refused (ACC-12-24 unit half);src/__tests__/fake-oidc-provider.spec.ts(new) — discovery, PKCE check, device grant, key rotation helper. Done when: the main bundle does not contain the fake provider (asserted on the build output).
P1.3 — Entity, sessions, migrations
-
T9.
ExternalIdentityentity and repository. Createpackages/agent/src/entities/external-identity.entity.tsandpackages/agent/src/database/repositories/external-identity.repository.ts(new, plan §3.1). Modifypackages/agent/src/entities/index.ts,packages/agent/src/database/_entity-names.ts,packages/agent/src/database/_entities-inventory.ts,packages/agent/src/database/_repository-inventory.ts. Test:packages/agent/src/entities/__tests__/external-identity.entity.spec.ts(new) — index names, Tier B columns, no token column (ACC-12-22 schema half);packages/agent/src/database/repositories/__tests__/external-identity.repository.spec.ts(new) — both conflict reasons, two concurrent inserts of one pair leave exactly one row (ACC-12-19), 10-client cap. Done when: the drift specs inpackages/agent/src/database/database.module.spec.tspass unchanged. -
T10. Session origin columns and
issueSessionargument. Modifypackages/agent/src/entities/auth-session.entity.ts(appendexternalIdentityId,externalSidand two indexes);apps/api/src/auth/providers/auth-provider.abstract.ts(optionaloriginargument);apps/api/src/auth/providers/auth-provider.service.ts(write the columns; exporthashSessionToken). Test: extendapps/api/src/auth/providers/auth-provider.service.spec.ts— existing callers write nulls;originis persisted; a session issued with an Ever ID origin hasexpiresAtexactly 7 days after creation, like a password session (ACC-12-27). Done when: every existing auth spec passes without edits. -
T11. Migrations. Create
apps/api/src/migrations/1792120000000-CreateExternalIdentities.tsandapps/api/src/migrations/1792120100000-AddExternalIdentityToSessions.ts(new, plan §3.6). Test:apps/api/src/migrations/__tests__/CreateExternalIdentities.spec.tsandapps/api/src/migrations/__tests__/AddExternalIdentityToSessions.spec.ts(new) —upcreates only the listed objects,downdrops only them, noDROP COLUMNof a pre-existing column. Done when: both migration specs pass, aDATABASE_AUTOMIGRATEboot on SQLite and on a Postgres service container migrates up and down cleanly, and — where a copy of stage's schema is available to the operator — the same up/down run is repeated against that copy and recorded in the operations change log. The SQLite and Postgres runs are the ones the PR lane can reproduce.
P1.4 — Facade and API services
-
T12.
IdentityProviderFacadeService. Createpackages/agent/src/facades/identity-provider.facade.ts(new); Modifypackages/agent/src/facades/facades.module.ts(FACADES) andpackages/agent/src/facades/index.ts. Test:packages/agent/src/facades/__tests__/identity-provider.facade.spec.ts(new) — admin-tier-only resolution; disabled/unconfigured/discovery-failing mapped toIdentityProviderUnavailableError. Done when: no plugin ID string appears outsidepackages/plugins/oidc-identity/. -
T13. Seal and replay services. Create
apps/api/src/auth/services/ever-id-seal.service.tsandapps/api/src/auth/services/ever-id-replay.service.ts(new, plan §3.3–3.4). Test:apps/api/src/auth/services/ever-id-seal.service.spec.ts(new) — tamper, wrong kind, expiry, 3,072-byte cap;apps/api/src/auth/services/ever-id-replay.service.spec.ts(new) — single use, concurrent inserts, ≤ 100-row cleanup; replaying a completed transaction yieldstransactionInvalid(ACC-12-09). Done when: ACC-12-09 holds at unit level. -
T14 (parallel with T13). Session service. Create
apps/api/src/auth/services/ever-id-session.service.ts(new). Test:apps/api/src/auth/services/ever-id-session.service.spec.ts(new) — bysidends only that session and by identity ends all it opened (ACC-12-23 unit half); except current on disconnect (ACC-12-20 unit half); password sessions untouched (ACC-12-25); counts returned. Done when: ACC-12-25 holds at unit level. -
T15. Linking service and Activity members. Create
apps/api/src/auth/services/ever-id-linking.service.ts(new, plan §5.5). Modifypackages/agent/src/entities/activity-log.types.ts(five additive members, plan §5.6;actionvalues dotted, Resolution R-2). Test:apps/api/src/auth/services/ever-id-linking.service.spec.ts(new) — the spec §5.3 decision tree as a table test (ACC-12-13…ACC-12-18); S3 returns no account ID;canDisconnecttruth table (ACC-12-20); sign-up records terms and creates the account, the connection and the session in that order, and a forcedinsertLinkfailure leaves no orphan account (ACC-12-14); the concurrent sign-up/connect race ends with one connection and the other outcome S12 (ACC-12-19); no row written by any path contains a token (ACC-12-22). Done when: ACC-12-13…ACC-12-20 and ACC-12-22 are proven at unit level.
P1.5 — Controller and guards
-
T16. Request-shape guards and
authMethod. (Resolution R-19) Createapps/api/src/auth/guards/no-token-in-query.guard.ts,apps/api/src/auth/guards/session-only.guard.ts(new). Modifyapps/api/src/auth/types/auth.types.ts— append exactly one value,'ever-id-delegated', to the existingauthMethodunion ('session' | 'api-key', shipped by AW-24). No other value and no new field is added, andapps/api/src/auth/guards/auth-session.guard.tskeeps every existing stamp. AW-24'sapps/api/src/safety/guards/human-actor.guard.tsadmits only'session', so it refuses delegated tokens on human-only routes without any change. Test:apps/api/src/auth/guards/no-token-in-query.guard.spec.ts(new) — each listed query key answers 400tokenInQuerybefore the handler and no logger call contains the value (ACC-12-10);apps/api/src/auth/guards/session-only.guard.spec.ts(new) —'api-key'and'ever-id-delegated'answer 403sessionRequired(ACC-12-21); extendapps/api/src/safety/guards/human-actor.guard.spec.tswith one case — a'ever-id-delegated'principal is refused.apps/api/src/auth/guards/auth-session.guard.spec.tsalready asserts the two existing stamps and is extended only for the delegated branch (T18). Done when: ACC-12-10 and ACC-12-21 hold at unit level, no existing guard assertion changed, and a grep ofapps/api/srcfinds no value other than'session','api-key'and'ever-id-delegated'onAuthenticatedUser.authMethod— scoped to that type and to the stamps inapps/api/src/auth/guards/auth-session.guard.ts, because unrelatedauthMethodfields exist elsewhere inapps/api/src(for example the Git-provider service's ownauthMethodvalues, which this epic does not touch). A type-level assertion on theauthMethodunion is the preferred form of the same check. -
T17.
EverIdController, DTOs, providers field. Createapps/api/src/auth/controllers/ever-id.controller.ts,apps/api/src/auth/dto/ever-id.dto.ts(new). Modifyapps/api/src/auth/auth.module.ts(controller + services), andapps/api/src/auth/controllers/auth.controller.ts(everIdfield onGET providers). Test:apps/api/src/auth/controllers/ever-id.controller.spec.ts(new) — every row of plan §5.1 and §5.2: unconfigured or disabled → 404 on every sign-in endpoint (ACC-12-01) while/identities,DELETEand/backchannel-logoutkeep working (ACC-12-04); throttle metadata values of spec FR-18 (ACC-12-11); areturnToto another site falls back to the dashboard (ACC-12-12); a valid notice withsidends that session and withsuball it opened, answering 200 withCache-Control: no-store(ACC-12-23); an invalid notice → 400 (ACC-12-24);GET /logout-urlreturns the end-session URL with astate(ACC-12-26); issued sessions carry the 7-day expiry (ACC-12-27); extendapps/api/src/auth/controllers/auth.controller.spec.ts—GET providerskeeps every existing field and addseverId(ACC-12-05). Done when: ACC-12-01, 04, 05, 11, 12, 23, 24, 26 and 27 pass at controller level. -
T18. Delegated read branch and
@DelegatedRead. (Resolution R-19) Createapps/api/src/auth/decorators/delegated-read.decorator.ts(new). Modifyapps/api/src/auth/guards/auth-session.guard.ts(plan §5.3) — the branch runs only for handlers carrying@DelegatedRead(scope)and stampsauthMethod: 'ever-id-delegated'. Test:apps/api/src/auth/guards/auth-session.guard.delegated.spec.ts(new) — a validapps:readtoken is admitted on a decorated test handler (ACC-12-33); the same token → 401 on an undecorated handler and 403insufficientScopeon a decorated handler requiring another scope (ACC-12-34); lifetime > 3,600 s or wrong audience → 401 (ACC-12-35); a decorated handler that is also@HumanOnly()refuses it through the unchangedHumanActorGuard. Coordinate: APW-11 applies the decorator to its list handler; this task ships a test controller in the spec only. Done when: ACC-12-33…ACC-12-35 hold against the test handler and a JWT bearer on any unmarked route still answers the pre-existing 401. -
T19. Local-client exchange and client configuration. Modify
apps/api/src/auth/controllers/ever-id.controller.ts— implementPOST /api/auth/ever-id/sessionandGET /api/auth/ever-id/client-config; modifyapps/api/src/auth/dto/ever-id.dto.tsfor their shapes. Test: extendapps/api/src/auth/controllers/ever-id.controller.spec.ts— unlistedazp, missing scope,iat> 300 s, reusedjtirefused (ACC-12-29); unconnected pair →403 notConnectedand no user row created (ACC-12-30);client-configreturns no secret. Done when: ACC-12-29 and ACC-12-30 hold at controller level. -
T20. API flow integration spec. (Resolution R-22 — replaces the former
apps/api/test/ever-id.e2e-spec.ts) Createapps/api/src/auth/ever-id.flow.integration.spec.ts(new, plan §10.3) — Jest, picked up byapps/api/jest.config.js(rootDir: src); in-memory better-sqlite3 withENTITIES, the real controller, services andAuthSessionGuard, the T8 fake provider andsupertest. Harness (the reason this task names it):apps/api/jest.config.jstransforms with ts-jest to CommonJS and maps no module for@ever-works/oidc-identity, whileopenid-clientandjoseare ESM-only packages that a CommonJSrequirecannot load — production loads plugins throughimport(), which Jest does not. So the spec bindsIdentityProviderFacadeServiceto an in-testIIdentityProviderPluginbuilt onnode:crypto(the same construction as T8's fake provider), added tojest.config.jsthrough amoduleNameMapperentry pointing at the fake provider's source path; the real plugin package is exercised by its own Vitest suite (T5–T8) instead. State explicitly in the spec header which of the two it uses, so the seam is never implicit. Modifyapps/api/jest.config.js(themoduleNameMapperentry above). Test: runcd apps/api && pnpm test -- ever-id.flow.integration. Assertions: a connected identity signs in and Activity holdsuser.login.ever-id(ACC-12-13 API half); sign-up confirm with terms creates the account, the connection and a session (ACC-12-14 API half);POST /sessionwith a freshly minted exchange token returns aTokenResponsein the body in < 5 s and no captured log line contains the token (ACC-12-28 API half); a test controller with@DelegatedRead('apps:read')returns the person's App Works (ACC-12-33 API half); sign-up, connect, disconnect and back-channel logout also pass end to end; and the two states the browser lane cannot reach are written directly here — a session row aged pastconnectMaxSessionAgeSeconds(S15, ACC-12-17 API half) and an account with no password, no social provider and an unverified e-mail (S14, ACC-12-20 API half). Done when: the spec passes in the PR lane and no file exists underapps/api/test/for this epic.
P1.6 — Web
-
T21. Fail-closed flag. (lands after APW-01 T20) Create
apps/web/src/lib/feature-flags/posthog-client.ts(extracted singleton) andapps/web/src/lib/feature-flags/ever-id.ts(new, plan §6.4); Modifyapps/web/src/lib/feature-flags/work-kinds.tsto import the extracted client only. Depends on APW-01 T20: that task createswork-kinds.unit.spec.tsand restructureswork-kinds.ts(FAIL_CLOSED_WORK_KINDS, off when PostHog is absent). APW-01's spec is the unchanged baseline this task must keep green; if APW-01 T20 has not landed, T21 writes a characterisation spec of today'swork-kinds.tsbehaviour first and keeps it after the extraction. Test:apps/web/src/lib/feature-flags/ever-id.flag.unit.spec.ts(new) — no key → configuration decides; error, timeout,undefined→ off (ACC-12-02); APW-01'swork-kinds.unit.spec.tsunchanged and green. Done when: ACC-12-02 holds and the extracted singleton leaves Work-kind evaluation byte-for-byte the same. Deliberate difference from APW-01, stated in plan §6.4: Work kinds read an absent PostHog key as off, Ever ID reads it as "configuration alone decides" — an authentication method must not be switched off by a missing analytics key (spec FR-1). The two helpers stay separate for exactly that reason. -
T22. Providers, API client, server actions, cookies. Modify
apps/web/src/lib/auth/providers.ts,apps/web/src/lib/api/auth.ts,apps/web/src/app/actions/auth.ts(the six actions of plan §6.1, plus the logout option). Createapps/web/src/lib/auth/ever-id-cookies.ts(new) —ew_everid_txn(600 s),ew_everid_pending(600 s, whose sealed value carries the identity the outcome is about, including theemailInUseaddress) andew_everid_logout_state(600 s), all HttpOnly, SameSite=Lax,securefrom the public URL scheme, Path/(the create-account and account-exists pages live under a locale prefix, so a narrow path would hide the cookie from them; T23's callback and T25's pages therefore read one path).confirmEverIdSignUpsends the terms claims of the account being created — the DTO shape of plan §5.1 — and takes the required documents from the same source the register page uses, so the web never invents adocumentId. Test: extendapps/web/src/app/actions/auth.unit.spec.ts—startEverIdSignInrejects an absolutereturnTo(ACC-12-12 web half); "Also sign out of Ever ID" redirects to the URL fromGET /logout-urland stores itsstateinew_everid_logout_state(ACC-12-26 web half);confirmEverIdSignUpposts terms claims and no action returns an upstream message verbatim; extendapps/web/src/lib/api/auth.unit.spec.ts— providers defaulteverId: { enabled: false }when absent;apps/web/src/lib/auth/ever-id-cookies.unit.spec.ts(new) — every cookie is HttpOnly, SameSite=Lax, Path/, and cleared on every callback, confirm and return path. Done when: no action returns an upstream error message verbatim (translated generic messages only), and the same cookie path is asserted in the unit spec, the callback route spec (T23) and the browser lane (T30). -
T23. Callback route and error codes. Create
apps/web/src/app/api/auth/ever-id/callback/route.ts(new). Modifyapps/web/src/app/[locale]/(auth)/auth/error/auth-error-content.tsx(ever_id_*codes). Test:apps/web/src/app/api/auth/ever-id/callback/route.unit.spec.ts(new) — outcome table of plan §6.3; the transaction cookie is cleared on every path; the address never carries the e-mail. Done when: the local-client hand-off route andaddSessionTokenToUrlhave no new caller (grep asserted in the spec). -
T24. Button on sign-in and registration. Create
apps/web/src/components/auth/ever-id-button.tsx(new). Modifyapps/web/src/app/[locale]/(auth)/login/login-client.tsx,apps/web/src/app/[locale]/(auth)/login/page.tsx, andapps/web/src/app/[locale]/(auth)/register/register-form.tsx(consent gate). Test:apps/web/src/components/auth/ever-id-button.unit.spec.tsx(new) — absent wheneverId.enabledis false or the flag is off (ACC-12-01 web half),Opening Ever ID…while redirecting; extendapps/web/src/app/[locale]/(auth)/register/register-form.unit.spec.tsx— disabled with "Accept the terms above to continue." until consent. Done when: the button is absent wheneverId.enabledis false or the flag is off. -
T25. Create-account and account-exists screens. Create
apps/web/src/app/[locale]/(auth)/auth/ever-id/create-account/page.tsx,apps/web/src/app/[locale]/(auth)/auth/ever-id/create-account/create-account-client.tsxandapps/web/src/app/[locale]/(auth)/auth/ever-id/account-exists/page.tsx(new). Test:apps/web/src/app/[locale]/(auth)/auth/ever-id/create-account/create-account-client.unit.spec.tsxandapps/web/src/app/[locale]/(auth)/auth/ever-id/account-exists/page.unit.spec.tsx(new) — terms required, cancel creates nothing (ACC-12-14 web half), pending cookie expiry copy, S3 copy without any account id (ACC-12-15 web half). Done when: spec §6.2 copy renders exactly. -
T26. Connected identities card, connect confirmation, sign-out dialog. Create
apps/web/src/components/settings/ConnectedIdentitiesCard.tsx,apps/web/src/app/[locale]/(dashboard)/settings/security/connect-ever-id/page.tsxandapps/web/src/components/auth/EverIdSignOutDialog.tsx(new). Modifyapps/web/src/components/settings/SecuritySettings.tsx,apps/web/src/components/dashboard/DashboardSidebar.tsx(handleLogout, line 127 — today it callslogout()straight from the menu item at line 616) andapps/web/src/components/command-palette/CommandPalette.tsx(line 192 — today it callslogout()straight from the command). Both callers route through the new dialog; when the session was not opened with Ever ID the dialog renders onlySign out/Canceland both callers behave exactly as they do today. No sign-out dialog exists ondevelop; this is an addition, not a replacement of one. Origin signal (already specified, no new route):getEverIdLogoutUrl()(T22) callsGET /api/auth/ever-id/logout-url, which answers404for a session not opened with Ever ID (plan §5.1) — a200is the signal that shows theAlso sign out of Ever IDcheckbox. The dialog asks once, on open. Return path: the API's end-session URL carries astate; T22 stores it inew_everid_logout_state(600 s, HttpOnly, SameSite=Lax,securefrom the public URL scheme, Path/, cleared on return), and the end-session call returns to newapps/web/src/app/api/auth/ever-id/logout-return/route.ts, which constant-time compares the state, clears the auth cookie and redirects to sign-in with the §6.5You're signed out of Ever Works and Ever ID.copy. The web tier validatesstatethe same wayhandleOAuthCallbackvalidates it; an absent or mismatched state falls back to the ordinary signed-out page with no message. The registered post-logout redirect URI is the row added toidp-options.md§6.1. Test:apps/web/src/components/settings/ConnectedIdentitiesCard.unit.spec.tsx(new) — connected, not connected, cannot disconnect, turned off, and the delegated apps list with its display name and last-used time (ACC-12-36);apps/web/src/components/auth/EverIdSignOutDialog.unit.spec.tsx(new) — the checkbox is absent whenGET /logout-urlanswers 404, ticked it follows the URL from that route, unticked it callslogout()exactly as today (ACC-12-26 web half);apps/web/src/app/api/auth/ever-id/logout-return/route.unit.spec.ts(new) — matching state clears cookies and shows the S7 copy, mismatched state degrades silently. Done when: ACC-12-36 renders from API data, the S14 variant disables Disconnect with its reason, and both existinglogout()callers still sign out with the checkbox unticked. -
T27. i18n. Modify
apps/web/messages/en.jsonwith every key of plan §8 and the other 20 locale files inapps/web/messages/. Plan §8 is the complete list: it carries the sign-in, sign-up, connect, card, dialog and error keys and the sign-out dialog (title, checkbox,Cancel,Sign out), the registration terms checkbox label, the pending-cookie expiry copy, the administrator §6.7 copy (Test connection, one label per FR-3 check id, the threeHealthlabels, the•••••• (set)secret placeholder), theaccountDisabledmessage thatauth.errordoes not have today, and oneauth.error.everId.*key for everyEverIdErrorCodethe web can receive (everIdDisabled,sessionRequired,notConnected,lastSignInMethod,accountDisabled,tokenInQuery,insufficientScopeincluded) — the mapping isever_id_<snake_code>→auth.error.everId.<camel>. Test:apps/web/src/lib/auth/ever-id-copy.unit.spec.ts(new) — every key present in every locale, no leaf contains a dot, no English value contains "SSO" or "single sign-on" (ACC-12-38), and the union ofEverIdErrorCodemaps onto a key inauth.error.everId.*with no member missing (a table-driven assertion, so a new code cannot ship untranslated). Done when: ACC-12-38 holds and the error-code coverage table is exhaustive.
P1.7 — Local clients
-
T28. CLI device sign-in. (after T44) Create
apps/cli/src/commands/auth/ever-id-device.service.ts(new); Modifyapps/cli/src/commands/auth/login.command.ts(--ever-id). Test:apps/cli/src/commands/auth/ever-id-device.service.spec.ts(new) — interval ≥ 5 s,slow_down+5 s (ACC-12-31), expiry ≤ 900 s, sign-in completes within 5 s of approval on a fake clock and no printed line contains the token (ACC-12-28), sanitised errors, exit code 1 on failure; T44's characterisation spec still passes (ACC-12-32). Done when: ACC-12-28, ACC-12-31 and ACC-12-32 hold (the existing browser login flow is untouched). -
T29 (parallel with T28). Node device sign-in. Modify
apps/node/src/core/auth-client.ts(signInWithEverId) andapps/node/src/core/runtime.ts. Test: extendapps/node/src/core/auth-client.spec.ts—protectis called before any other use of the access token; the session is returned in a body and no log line contains the token (ACC-12-28 node half). Done when: the e-mail/password path's tests pass unchanged.
P1.8 — End to end, docs, ship gate
-
T30. Playwright suites. Create in
apps/web/e2e/(new):ever-id-sign-in.spec.ts,ever-id-sign-up.spec.ts,ever-id-connect.spec.ts,ever-id-backchannel-logout.spec.ts,ever-id-disabled.spec.ts,ever-id-a11y.spec.ts(plan §10.4). Test: runpnpm --filter ever-works-web test:e2e ever-id. Assertions: S1 sign-in with Activity (ACC-12-13), replayed callback → S17 (ACC-12-09), foreignreturnTo→ dashboard (ACC-12-12); S2 with terms (ACC-12-14), S3 (ACC-12-15), S11 (ACC-12-16); within 300 sauth_timesucceeds (ACC-12-17 browser half), S12 (ACC-12-18), disconnect keeps the current session (ACC-12-20); sign-out notice ends two Ever ID sessions ≤ 5 s and a password session survives, replayedjti→ 400 (ACC-12-23, ACC-12-24, ACC-12-25); the S6 notice renders on the next page load after a back-channel notice (ACC-12-23 browser half); disabled: no button, sign-in endpoints 404, card and notices still work (ACC-12-01, ACC-12-04); axe over button, both confirmation screens, card and dialogs (ACC-12-39). Harness limits, stated so the two unrunnable assertions move instead of being dropped: the PR lane's API is an in-memory SQLite database inside the API process, so the browser suite cannot age asessionrow pastconnectMaxSessionAgeSeconds(S15 / ACC-12-17) and cannot create the S14 account (no password, no social provider, unverified e-mail). Both are proven in T20's integration spec, which writes the rows directly;ever-id-connect.spec.tskeeps the reachable half of S15 (an expiredauth_time→ thereauthRequiredcopy) and the S14 card state, which arrives through the API response rather than the database. Done when: all pass, andauth.spec.ts,auth-providers-list.spec.ts,auth-clock-tolerance.spec.tsanddevice-auth.spec.tspass unchanged (ACC-12-05, ACC-12-39). -
T31. Wire acceptance scenarios. Modify
docs/specs/features/app-works/ACCEPTANCE.md(owned by the program; send the rows to its owner if the PR cannot edit it) with the APW-12 section mapping ACC-12-01…ACC-12-40 and XP-T/XP-G ids to the test files named in T6–T30, T44, T45 andcross-platform.md§7. Test: a review check that everyACC-12-id of spec §8 appears in a Test line of this file and in ACCEPTANCE.md with a file path (grep -o "ACC-12-[0-9]*" spec.md tasks.md | sort -ucompared). Done when: every ACC-12 ID maps to at least one test file. -
T32. Admin health and telemetry. Modify
apps/api/src/auth/controllers/ever-id.controller.ts— implementPOST /admin/testandGET /admin/health. Createpackages/monitoring/src/posthog/ever-id-events.ts(new) — the closed event union of plan §9.1, followingpackages/monitoring/src/posthog/kb-events.ts; modifypackages/monitoring/src/posthog/index.tsto export it. Availability and health are shared state, not per-process state (spec FR-5, FR-14). Plugin availability today is registry state inside one API process (packages/agent/src/facades/oauth.facade.tschecksstate === 'loaded'), so an issuer-drift lock-out set on one replica and a re-test run on another would disagree, andGET /admin/healthwould answer differently per replica. This task therefore persistsunavailableSinceplus the three health timestamps (discoveryRefreshedAt,jwksRefreshedAt,lastLogoutNoticeAt) in the plugin's own settings row (the persisted settings JSON the plugin already owns), reads them through a cache of at most 60 seconds so FR-5's disable bound holds, and clearsunavailableSinceonly whentestConnectionpasses. No new table. Test:apps/api/src/auth/services/ever-id-telemetry.spec.ts(new) — every §9.1 payload built by the services contains no e-mail, subject, issuer URL, token, code orstate(ACC-12-37 telemetry half);packages/monitoring/src/posthog/__tests__/ever-id-events.spec.ts(new) pins the event names; extendapps/api/src/auth/controllers/ever-id.controller.spec.ts— both admin routes refuse non-admins, atestConnectionfailure recordsunavailableSinceand a later green run clears it, and two service instances sharing the persisted settings see the same availability and the same health timestamps (the replica-agreement case). Done when: ACC-12-37 holds for telemetry as well as Activity (T45), and a second instance reading the store observes a disable within 60 s without a restart. -
T33. Docs. Create
docs/features/ever-id.md(new; what Ever ID is on Ever Works, connecting, disconnecting, terminal sign-in; the copy rule applies). Modifydocs/plugin-system/built-in-plugins.md(addoidc-identity, Constitution VIII),docs/features/api-keys.md(one line pointing terminals to device sign-in),docs/specs/features/app-works/README.md§1 (confirm the Connected identity row),TRACKER.md(APW-12 P1 status). Adddocs/features/ever-id.mdtoapps/docs/sidebarsPlatform.tsonly in the PR that enables Ever ID in production (G-09 copy rule). Test: runpnpm --filter ever-works-docs build;grep -i "sso\|single sign-on" docs/features/ever-id.mdfinds nothing. Done when:pnpm --filter ever-works-docs buildhas no broken links. -
T34. P1 ship gate. Root
format,lint,type-check,test,buildgreen; ACC-12-01…ACC-12-39 walked on stage with the real provider; rollout per plan §11 (disabled → staff via flag → general). Modifydocs/specs/features/app-works/TRACKER.md— APW-12 P1 row. Test: the PR lanes run every spec named in T3–T30, T44 and T45; the golden-pathapps/web/e2e/flow-ever-id-switch.spec.ts(APW-13, ACC-E2E-13 (a)) passes against stage. Done when: the tracker row reads P1Verified.
Phase P2 — Ever Teams (cross-platform.md §4)
-
T35. Gauzy API slice, default off (tracked in
ever-co/ever-gauzy). Createpackages/core/src/lib/auth/external-identity/(entity, module, service, TypeORM and MikroORM repositories,ever-id-token.service.ts) and one migration inpackages/core/src/lib/database/migrations/. Modifypackages/contracts/src/lib/feature.model.ts(FEATURE_EVER_ID_API),packages/common/src/lib/guards/feature-flag-enabled.guard.ts(strict'true'),packages/core/src/lib/auth/auth.controller.tsandpackages/core/src/lib/auth/auth.service.ts(the four routes and token metadata of cross-platform §4.1). Test (tracked inever-co/ever-gauzy, runyarn nx test coreandyarn nx test common):packages/core/src/lib/auth/external-identity/ever-id-token.service.spec.ts(same rejection table as T7);packages/core/src/lib/auth/external-identity/external-identity.service.spec.ts(XP-T-03);packages/core/src/lib/auth/external-identity/ever-id-backchannel-logout.spec.ts(XP-T-04);packages/core/src/lib/auth/auth.controller.ever-id.spec.ts(XP-T-01, XP-T-02, XP-T-06);packages/common/src/lib/guards/feature-flag-enabled.guard.spec.ts(XP-T-06). Done when: XP-T-06 holds and Gauzy's existing auth suites (auth.service.login-attempt.spec.ts,auth.service.register-employee.spec.ts,strategies/jwt.strategy.spec.ts) pass unchanged. -
T36. Ever Teams sign-in and connect (tracked in
ever-co/ever-teams). Modifyapps/web/core/lib/utils/check-provider-env-vars.ts,apps/web/core/types/generics/enums/social-accounts.ts,apps/web/core/services/server/requests/auth.ts,apps/web/auth.ts,apps/web/core/services/server/requests/o-auth.ts,apps/web/core/components/auth/social-logins-buttons.tsx,apps/web/app/[locale]/(main)/settings/personal/page.tsx(cross-platform §4.2). Createapps/web/cypress/support/mock-ever-id-provider.mjsbeside the existingmock-gauzy-server.mjs. Test (tracked inever-co/ever-teams; Jest viayarn test:web, Cypress viayarn e2e:web):apps/web/core/lib/utils/check-provider-env-vars.test.ts(XP-T-06),apps/web/core/services/server/requests/o-auth.test.ts(XP-T-02),apps/web/core/services/server/requests/auth.test.ts(token only in theAuthorizationheader),apps/web/auth.test.ts(XP-T-01, XP-T-02),apps/web/app/[locale]/(main)/settings/personal/page.test.tsx(XP-T-03); Cypressapps/web/cypress/e2e/ever-id-sign-in.cy.ts(XP-T-01, XP-T-02, XP-T-05),apps/web/cypress/e2e/ever-id-connect.cy.ts(XP-T-03),apps/web/cypress/e2e/ever-id-backchannel-logout.cy.ts(XP-T-04) — against the fake provider and a Gauzy development API. Done when: XP-T-01…XP-T-05 hold on Teams stage. -
T37 (parallel with T36). App Launcher token for Teams (tracked in
ever-co/ever-teams). Createapps/web/app/api/auth/ever-id/token/route.ts— a same-origin route returning the current Ever ID access token for APW-11'sgetAccessToken()(body only,Cache-Control: no-store, ≤ 900 s left). Test (tracked inever-co/ever-teams):apps/web/app/api/auth/ever-id/token/route.test.ts— no token in the URL or headers other than the body,Cache-Control: no-store, refuses a token with ≤ 0 s left (XP-T-05). Done when: APW-11's delegated read works from Teams stage and ACC-12-33 holds cross-origin. -
T38. P2 rollout gate. (operator attestation) Owner approval recorded;
FEATURE_EVER_ID_API=trueon Gauzy production with only the Teams client trusted; Teams production enabled; existing Teams and Gauzy sign-ins exercised end to end before and after. Modifydocs/specs/features/app-works/TRACKER.md— APW-12 P2 row (flags are operator configuration, not files). Test: T35–T37's suites green in both repositories on the released commits; the before/after sign-in runs are recorded in the private operations change log asapw12-p2-rollout. Done when: XP-T-01…XP-T-06 verified in production and the tracker row reads P2Verified(ACC-12-40 for Ever Teams).
Phase P3 — Ever Gauzy (cross-platform.md §5)
-
T39. Gauzy API sign-in with hand-off (tracked in
ever-co/ever-gauzy). Createpackages/auth/src/lib/ever-id/ever-id.strategy.ts,ever-id.controller.ts,index.ts, andpackages/core/src/lib/auth/external-identity/ever-id-handoff.service.ts. Modifypackages/auth/src/lib/internal.ts,packages/contracts/src/lib/feature.model.ts(FEATURE_EVER_ID_LOGIN),packages/common/src/lib/guards/feature-flag-enabled.guard.ts,packages/core/src/lib/auth/auth.controller.ts(POST /auth/signin.ever-id.handoff). Test (tracked inever-co/ever-gauzy, runyarn nx test authandyarn nx test core):packages/auth/src/lib/ever-id/ever-id.strategy.spec.ts(S256, nonce — XP-G-03),packages/auth/src/lib/ever-id/ever-id.controller.spec.ts(callback address has no token or user id — XP-G-03; flag unset → 404 — XP-G-04),packages/core/src/lib/auth/external-identity/ever-id-handoff.service.spec.ts(single use, 60 s, verifier — XP-G-02). Done when: XP-G-02…XP-G-04 hold. -
T40. Gauzy web UI (tracked in
ever-co/ever-gauzy). Modifypackages/ui-auth/src/lib/components/social-links/social-links.component.ts(+ template),packages/ui-auth/src/lib/auth.routes.ts,packages/ui-core/shared/src/lib/user/edit-profile-form/edit-profile-form.component.ts. Createpackages/ui-auth/src/lib/components/ever-id-complete/. Test (tracked inever-co/ever-gauzy): extendpackages/ui-auth/src/lib/components/social-links/social-links.component.spec.ts(no Ever ID link unless the flag is exposed — XP-G-04),packages/ui-auth/src/lib/components/ever-id-complete/ever-id-complete.component.spec.ts(redeems the hand-off, routes to workspace selection — XP-G-01); Playwrightapps/gauzy-e2e/tests/ever-id-sign-in.spec.ts(sign-in to one of two linked workspaces, an unlinked one absent — XP-G-01), runyarn nx e2e gauzy-e2e. Done when: XP-G-01 holds on Gauzy stage. -
T41. MCP authorization server federated login (tracked in
ever-co/ever-gauzy). Modifypackages/auth/src/lib/mcp/server/oauth-authorization-server.tsandapps/mcp-auth/src/mcp-oauth/mcp-oauth.service.ts(cross-platform §5.3). Test (tracked inever-co/ever-gauzy, runyarn nx test authandyarn nx test mcp-auth):packages/auth/src/lib/mcp/server/oauth-authorization-server.ever-id.spec.ts(a PKCE authorization completed through Ever ID login; e-mail/password login unchanged with the flag off and on) andapps/mcp-auth/src/mcp-oauth/mcp-oauth.service.spec.ts(flag unset → no federated route) (XP-G-05). Done when: XP-G-05 holds on stage. -
T42. P3 rollout gate — production last. (operator attestation) Backups verified per the operations runbook; every existing Gauzy sign-in method exercised on stage; owner approval;
FEATURE_EVER_ID_LOGIN=truein production, thenMCP_AUTH_EVER_ID_ENABLED=truein a separate change; rollback is a flag flip. Modifydocs/specs/features/app-works/TRACKER.md— APW-12 P3 row (flags are operator configuration, not files). Test (tracked inever-co/ever-gauzy): existingapps/gauzy-e2e/tests/login.smoke.spec.tsandapps/gauzy-e2e/tests/bdd/features/login.featureagainst stage plus each configured social sign-in (XP-G-06); run links and the backup verification recorded in the private operations change log asapw12-gauzy-stage-signin-regression. Done when: XP-G-06 and ACC-12-40 are verified and the tracker row reads P3Verified.
Cross-phase closing tasks
-
T43. Statuses. Modify
docs/specs/features/app-works/APW-12-ever-id/spec.md,plan.md,tasks.mdandcross-platform.md—StatustoImplemented/Done; confirm every gate in plan §12 against merged code, and keep the "known gaps" list current. Test:grep -n "Status" docs/specs/features/app-works/APW-12-ever-id/*.mdshowsImplementedorDoneon the four files;npx prettier --checkpasses on them. Done when: plan §12 has no unticked item and TRACKER.md shows APW-12 P1–P3Verified. -
T44. Characterise the existing terminal browser sign-in. (lands before T28) Create
apps/cli/src/commands/auth/__tests__/login.command.browser-flow.spec.ts(new) — no production code change. Test: that spec —loginwithout--ever-idstill runs the existing loopback browser hand-off ofapps/cli/src/commands/auth/oauth.service.ts, accepts the credential it returns and stores it exactly where it does today;--manualstill prompts for a token; neither path imports or callsever-id-device.service.ts(ACC-12-32); runpnpm --filter ever-works-cli test. The hand-off's mechanism is not restated here: this public repository describes existing, unfixed weaknesses generically (Resolution R-14), and a task that must not touch a file names the file and nothing more. The exact reproduction lives in the private operations repository. Done when: the spec passes ondevelopbefore T28 and unchanged after T28. -
T45. Activity rows audit (FR-49). Create
apps/api/src/auth/services/ever-id-activity.spec.ts(new) — no production code change unless it fails. The configuration-change row is driven through the plugin-settings update path (PluginsController's settings save, which already logs the genericPLUGIN_CONFIGUREDevent for every plugin): a listener filtered to theidentity-providercapability writesIDENTITY_PROVIDER_CONFIG_CHANGED(plan §5.6); it is installed by the task that adds the administrator surface (T51), and this spec drives it by saving settings through that controller's service, not by callingActivityLogServiceitself. Test: that spec drives sign-in, sign-up, connect, disconnect, a back-channel notice, device sign-in, a first delegated read and a configuration change through the services with a capturingActivityLogService: theactionvalues equal plan §5.6's eight (each dotted, with the snake-caseactionTypefamily, Resolution R-2), a second delegated read by the same client within 24 h adds no row, and no serialised row contains a planted token, code, subject orstate(ACC-12-37); runcd apps/api && pnpm test -- ever-id-activity. Done when: the spec passes and removing any one Activity call from the services makes it fail. -
T46 (P1, lands with T9–T11). Classify new tables for workspace backup (R-25). Modify
packages/agent/src/account-transfer/backup/redaction.ts—BACKUP_DROPPED_ENTITIESgainsExternalIdentity, besideAuthSessionandAuthAccountunder the sessions-and-auth comment: an issuer + subject link is a sign-in binding, and a restore must never re-link an account to an identity.packages/agent/src/account-transfer/backup/collectors/domain-specs.tsis not modified. T10'sexternalIdentityIdandexternalSidneed no entry:AuthSession(tablesession) is already dropped entirely. Test: extendpackages/agent/src/account-transfer/backup/collectors/collectors.spec.ts—ExternalIdentityandAuthSessionare inBACKUP_DROPPED_ENTITIESand referenced by no domain file;redaction.spec.ts'sit.each(BACKUP_DROPPED_ENTITIES)covers the new entry with no edit. Done when:pnpm --filter @ever-works/agent test -- collectors redactionis green and a backup of a workspace whose owner connected Ever ID contains noExternalIdentityrow and not the linked subject anywhere in the archive.
Appended tasks (additive — added 2026-09-17; the tasks above keep their numbers)
-
T47 (P1, after T6). Close the provider gate with the in-product Test connection. Runs the Ever Works administrator Test connection (T6, the
POST /admin/testroute of plan §5.1) against the development provider and records the result in the private operations change log asapw12-provider-standup, completing the half of T2 that needs P1 code. T2 itself closes on the manual discovery-document read, so P0 no longer waits for P1 and P1 no longer waits on a tick that needs P1. Modify nothing in this repository;docs/specs/features/app-works/TRACKER.mdgets the APW-12 P0 note only after this run and T2's manual read both hold. Test: every FR-3 check returnsok: truein one run ofPOST /admin/testagainst the development provider, and the same run's copy matches spec §6.7 one row per check id. Done when: the recorded run shows every check green, or the failing check is filed as a defect against the provider's configuration and D1 is revisited peridp-options.md§5 (a re-run of the §4 scoring, not a rewrite of the relying parties). -
T48 (P1). Wire the fake identity provider and the plugin into the Playwright PR lane.
ACCEPTANCE.md§0.2 requires theever-idflag on, withoidc-identityconfigured against the fake provider, for theever-id-*.spec.tssuites — and nothing builds that lane today: the plugin isautoEnable: false(plan §4.2), the fake starts "on a free port" (plan §10.4), the API reads its issuer from settings orEVER_ID_ISSUER_URL(spec FR-14 turns sign-in off on issuer drift), and.github/workflows/e2e.yml's API env block carries noEVER_ID_*variable. Additive: the existing GitHub fake (APW-13 T13) is untouched. Createapps/web/e2e/helpers/ever-id.ts(new) — starts the T8 fake provider on a fixed port before the API boots, writes its issuer into the lane's API environment and enables the plugin once through the admin plugin API (the same call the administrator surface uses, T51), and exposesdisableEverId()/enableEverId()for the disabled spec. Modify.github/workflows/e2e.yml(the API env block:EVER_ID_ISSUER_URL,EVER_ID_CLIENT_ID,EVER_ID_CLIENT_SECRETpointing at the fake, and the port the helper uses),apps/web/e2e/global-setup.ts,apps/web/package.json(the workspace dependency that exposes the plugin's./testingexport) andapps/web/playwright.config.tsif the helper needs a project ordering. Test:apps/web/e2e/ever-id-sign-in.spec.tspasses in the PR lane with the plugin disabled at boot and enabled by the helper — proving the issuer the API validates is the fake's fixed issuer, not a drifted one; andever-id-disabled.spec.tsturns the plugin off per test through the same helper and restores it inafterEach, so no spec depends on ordering. Done when: the wholeever-id-*suite runs green in onepnpm --filter ever-works-web test:e2e ever-idinvocation in the PR lane, with noEVER_ID_*value pointing anywhere but the in-lane fake. -
T49. Activity surfaces for the five new action types. The five additive members of plan §5.6 are new to the web: today
apps/web/src/components/activity-log/ActivityTypeBadge.tsx(TYPE_COLORS,TYPE_TO_I18N) andapps/web/src/components/activity-log/ActivityFilters.tsx(ACTION_TYPES) carry no entry foridentity_linked,identity_unlinked,user_logout,delegated_accessoridentity_provider_config_changed, so those rows would render with the default badge and no filter label. Modifyapps/web/src/components/activity-log/ActivityTypeBadge.tsx,apps/web/src/components/activity-log/ActivityFilters.tsxandapps/web/messages/en.json(plus the other 20 locale files) with one colour, one i18n label and one filter entry per member; the summary string of each row comes from the API (§5.6) and is rendered as it arrives — the web does not re-derive it. Test: extendapps/web/src/components/activity-log/ActivityTypeBadge.unit.spec.tsxandActivityFilters.unit.spec.tsx(or create them) with a table over everyActivityActionTypemember, asserting a badge colour and a non-default label for each — so a future member cannot ship unlabelled; runpnpm --filter ever-works-web test -- activity-log. Done when: an Activity list containing all eight FR-49 rows renders eight distinct labels, and the exhaustive table fails if a member is added without a surface. -
T50. Node sign-in with Ever ID has a person-facing caller. FR-39 says a node can sign in with device authorization, and T29 changes
apps/node/src/core/auth-client.ts— but the only caller of node credential sign-in today is the desktop app (apps/desktop-node/src/main/main.ts:262,enrollNodeWithCredentials({ … })with e-mail and password), which has no device-code path and no prompt, so the node half of ACC-12-28 cannot be observed by a person. Additive: the e-mail/password enrolment stays exactly as it is and remains the default. Modifyapps/desktop-node/src/main/main.ts(a second enrolment mode beside the credentials branch),apps/desktop-node/src/shared/ipc-contract.ts(the channel carrying the verification address, the user code and the outcome),apps/desktop-node/src/renderer/wizard/WizardView.tsxandapps/desktop-node/src/renderer/wizard/steps.ts(a step that shows the verification address and code and waits). Create none outside those files except the spec below. Test: extendapps/desktop-node/src/renderer/wizard/steps.spec.tsand addapps/desktop-node/src/main/ever-id-enrol.spec.ts(new) — the device mode prints only the verification address and the code, the access token is dropped after the exchange, no log line or renderer state contains it, and the credentials mode is byte-for-byte unchanged (ACC-12-28 node half). If the owner prefers the Ever ID path to be CLI-only for nodes, this task is discharged by naming the node CLI entry point and its spec instead — the choice is recorded here rather than left implicit. Done when: a person can complete node enrolment with Ever ID from the desktop app, and the existing credentials enrolment passes its tests unchanged. -
T51 (P1). Administrator surface for Ever ID, and the configuration-change Activity row. Spec S10 and §6.7 describe a Test connection button with one row per FR-3 check, a Health section and a
•••••• (set)secret display; the plan has the two API routes (T32) but no page, and FR-4 requires an Activity row for every configuration change. The existing genericPOST plugins/:pluginId/validate-connectionreturnsConnectionValidationResult { success, message }— one line, not the per-check rows the spec asks for — and settings saves already emit the genericPLUGIN_CONFIGURED; nothing emitsauth.ever_id.config_changedyet, although T45 drives "a configuration change through the services". Createapps/web/src/app/[locale]/(dashboard)/settings/admin/ever-id/page.tsxand its client (new), rendering the FR-2 settings form, the Test connection rows fromPOST /api/auth/ever-id/admin/test, the Health block fromGET /api/auth/ever-id/admin/health, and the secret as•••••• (set). Modify the settings surface's navigation/registration, the plugin-settings save path (a listener filtered to theidentity-providercapability that writesIDENTITY_PROVIDER_CONFIG_CHANGEDwith{ fields }metadata — the genericPLUGIN_CONFIGUREDrow stays as it is), andapps/web/messages/en.jsonplus the other 20 locale files for the §6.7 copy (T27). Test:apps/web/src/app/[locale]/(dashboard)/settings/admin/ever-id/page.unit.spec.tsx(new) — one row per check id with its✓/✗copy, the Health labels, the secret never rendered; extend theever-id-activityspec (T45) so a save through this path writes exactly oneauth.ever_id.config_changedrow listing the changed fields and no secret; the page passes the same axe check as the card (ACC-12-39). Done when: an administrator can change a setting and see the change in Activity, and the connection test renders one row per check without exposing the secret. -
T52 (P2 and P3, before T36 and T39). File the cross-repository issues. The Teams and Gauzy halves of this epic are tracked in
ever-co/ever-teamsandever-co/ever-gauzy, where nothing exists today — no issue, branch or spec. The issue bodies are drafted:cross-repo-issues/ever-teams.mdandcross-repo-issues/ever-gauzy.md, with the index and the rules both repositories follow incross-repo-issues/README.md. Modifydocs/specs/features/app-works/APW-12-ever-id/tasks.md(T31) so theXP-T-*/XP-G-*rows carry the issue links once they exist. Test: a review check — each drafted issue body's cited paths were re-opened at the target repository's current head before filing, and the index table carries the filed issue numbers. Done when: both issues exist, are linked from the index, and the Gauzy one records that its production flag is a separate owner-approved change (cross-platform.md§5, T42). -
T53 (P2, with APW-11). Confirm the launcher mounts in one other Ever platform. APW-11 T28's Done-when requires at least one other Ever platform to load the published launcher; the Teams token route is T37 here, and the mount itself lives in
APW-11/cross-platform.md(APW-11 owns the component and the header mount; this epic owns the token it reads). Until that file exists, the mount is unnamed and the launcher's cross-origin read cannot be verified end to end. Test (tracked inever-co/ever-teams): the mount renders the launcher for a signed-in person andapps/web/app/api/auth/ever-id/token/route.test.ts(T37) proves the token never travels in a URL; the launcher's delegated read then answers for that person's App Works (ACC-12-33 cross-origin half). Done when: one Ever platform other than Ever Works renders the launcher with live tiles, and the token route's body-only guarantee is asserted there.
Definition of Done
- Every checkbox above is ticked.
pnpm format:check,pnpm lint,pnpm type-check,pnpm testandpnpm buildare green from the Ever Works repository root; Ever Teams and Ever Gauzy CI are green for their tasks.- Every pre-existing sign-in test in all three repositories passes unchanged.
- ACC-12-01…ACC-12-40, XP-T-01…XP-T-06 and XP-G-01…XP-G-06 have been walked against running builds.
- No hostname, address or secret was added to any public repository.
- No suite for this epic lives under
apps/api/test/(Resolution R-22). - Every gate in plan §12 is confirmed, and the carried-forward gaps are still recorded there.