Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 33 additions & 21 deletions .agents/skills/verify-interface/SKILL.md

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions .agents/skills/verify-interface/calls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { equal } from "@antelopejs/tooling-configs/verify";
import type { InterfaceHooks } from "@antelopejs/tooling-configs/verify";

import type * as AuthInterface from "../../../src/index.ts";

type PlainJson = Parameters<typeof equal>[2];

/** Copies a value of the interface's JSON types into the pack's, dropping the `undefined` members the interface allows. */
function plain(value: AuthInterface.JsonValue): PlainJson {
if (Array.isArray(value)) return value.map(plain);
if (!(value instanceof Object)) return value;
return Object.fromEntries(Object.entries(value).flatMap(([key, member]) => (member === undefined ? [] : [[key, plain(member)] as const])));
}

/** The plain functions the consumer calls through core, each with the value it must resolve to. */
export const CALLS: NonNullable<InterfaceHooks<typeof AuthInterface>["calls"]> = {
".:ValidateRaw": {
call: async (api) => plain(await api.ValidateRaw("tok", { maxAge: "1h" })),
expect: { stub: ".:internal.Verify", args: ["tok", { maxAge: "1h" }] },
},
".:SignRaw": { call: (api) => api.SignRaw({ userId: 1 }, { expiresIn: "1h" }), expect: { stub: ".:internal.Sign", args: [{ userId: 1 }, { expiresIn: "1h" }] } },
".:SignServerResponse": {
call: async (api) => {
const headers: Record<string, string> = {};
// SAFETY: SignServerResponse only calls setHeader on the response.
const res = { setHeader: (name: string, value: string) => void (headers[name] = value) } as never;
const returned = await api.SignServerResponse(res, "payload", undefined, { httpOnly: true });
return { headers, same: returned === res };
},
expect: { headers: { "Set-Cookie": "ANTELOPEJS_AUTH=[object Object]; httpOnly" }, same: true },
},
".:CreateAuthDecorator": { call: async (api) => api.CreateAuthDecorator({})().length, expect: 3 },
".:Authentication": { call: async (api) => api.Authentication().length, expect: 3 },
".:internal.defaultSource": {
// SAFETY: defaultSource reads only the headers of the request.
call: async (api) => api.internal.defaultSource({ headers: { cookie: "ANTELOPEJS_AUTH=c" } } as never, {} as never) ?? null,
expect: "c",
},
".:internal.defaultAuthenticator": {
call: async (api) => plain(await api.internal.defaultAuthenticator("tok", {})),
expect: { stub: ".:internal.Verify", args: ["tok", {}] },
},
".:internal.CheckAuthentication": {
call: (api) =>
api.internal.CheckAuthentication(
// SAFETY: the source and the authenticator below never read the request.
{} as never,
// SAFETY: the source and the authenticator below never read the response.
{} as never,
{ source: () => "t", authenticator: (data) => `verified:${data}`, authenticatorOptions: {}, validator: (claims) => `validated:${claims}` },
),
expect: "validated:verified:t",
},
};
45 changes: 3 additions & 42 deletions .agents/skills/verify-interface/verify.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { equal } from "@antelopejs/tooling-configs/verify";
import type { InterfaceHooks, Journey, VerifyConfig } from "@antelopejs/tooling-configs/verify";
import type { Journey, VerifyConfig } from "@antelopejs/tooling-configs/verify";

import type * as AuthInterface from "../../../src/index.ts";

import { CALLS } from "./calls.ts";

/** The setup every scenario shares: the packed interface, a recording stand-in provider behind `internal.Sign` and `internal.Verify`, and small helpers. */
const SCENARIO_PRELUDE = `
const [dist, repo] = process.argv.slice(2);
Expand Down Expand Up @@ -236,47 +238,6 @@ type _Authentication = Assert<Identical<Parameters<typeof Api.Authentication>, [
type _Decorators = Assert<Identical<ReturnType<typeof Typed>, ReturnType<typeof Api.Authentication>>>;
`;

/** The plain functions the consumer calls through core, each with the value it must resolve to. */
const CALLS: NonNullable<InterfaceHooks<typeof AuthInterface>["calls"]> = {
".:ValidateRaw": {
call: (api) => api.ValidateRaw("tok", { maxAge: "1h" }),
expect: { stub: ".:internal.Verify", args: ["tok", { maxAge: "1h" }] },
},
".:SignRaw": { call: (api) => api.SignRaw({ userId: 1 }, { expiresIn: "1h" }), expect: { stub: ".:internal.Sign", args: [{ userId: 1 }, { expiresIn: "1h" }] } },
".:SignServerResponse": {
call: async (api) => {
const headers: Record<string, string> = {};
// SAFETY: SignServerResponse only calls setHeader on the response.
const res = { setHeader: (name: string, value: string) => void (headers[name] = value) } as never;
const returned = await api.SignServerResponse(res, "payload", undefined, { httpOnly: true });
return { headers, same: returned === res };
},
expect: { headers: { "Set-Cookie": "ANTELOPEJS_AUTH=[object Object]; httpOnly" }, same: true },
},
".:CreateAuthDecorator": { call: (api) => api.CreateAuthDecorator({})().length, expect: 3 },
".:Authentication": { call: (api) => api.Authentication().length, expect: 3 },
".:internal.defaultSource": {
// SAFETY: defaultSource reads only the headers of the request.
call: (api) => api.internal.defaultSource({ headers: { cookie: "ANTELOPEJS_AUTH=c" } } as never, {} as never) ?? null,
expect: "c",
},
".:internal.defaultAuthenticator": {
call: async (api) => api.internal.defaultAuthenticator("tok", {}),
expect: { stub: ".:internal.Verify", args: ["tok", {}] },
},
".:internal.CheckAuthentication": {
call: (api) =>
api.internal.CheckAuthentication(
// SAFETY: the source and the authenticator below never read the request.
{} as never,
// SAFETY: the source and the authenticator below never read the response.
{} as never,
{ source: () => "t", authenticator: (data) => `verified:${data}`, authenticatorOptions: {}, validator: (claims) => `validated:${claims}` },
),
expect: "validated:verified:t",
},
};

const config: VerifyConfig<typeof AuthInterface> = {
doors: {
data: null,
Expand Down
9 changes: 0 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,6 @@ jobs:
test-job: true
permissions:
contents: read

checks:
if: always()
needs: [ci]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')
run: exit 1
renovate-sync:
if: >-
github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/') && github.event.pull_request.head.repo.full_name == github.repository
Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ Verify a boundary against the imports a developer or agent would plausibly write
| A CI gate reads `needs.<id>` only of jobs in its `needs` | `antelopejs-sync --check` |
| Entry-only rules exempt no tracked outsider | `antelopejs-sync --check` |
| A DMS layer's `build/backend-boundary.ts` is the pack's plugin | `antelopejs-sync --check` |
| No `skipLibCheck` in the root tsconfig or one `typecheck` or `build` uses | Reported by `antelopejs-sync` (note only) |
| `build` skips the lib check only over what `typecheck` checks | Reported by `antelopejs-sync` (note only) |
| `release` runs `typecheck` | Reported by `antelopejs-sync` (note only) |
| A test names the wrong implementation it catches | review; no tool |

<!-- antelopejs-guardrails:end -->
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"test": "pnpm run build && ajs module test .",
"format:check": "oxfmt --check .",
"knip": "knip",
"prepare": "if git rev-parse --is-inside-work-tree >/dev/null 2>&1 && command -v lefthook >/dev/null 2>&1; then lefthook install; fi",
"prepare": "if [ -e .git ] && command -v git >/dev/null 2>&1 && command -v lefthook >/dev/null 2>&1 && GIT_DIR=.git GIT_WORK_TREE=. git rev-parse --git-dir >/dev/null 2>&1; then GIT_DIR=.git GIT_WORK_TREE=. lefthook install; fi",
"checks": "antelopejs-checks",
"size": "antelopejs-check-size",
"sync": "antelopejs-sync",
Expand All @@ -61,7 +61,7 @@
"@antelopejs/core": "^1.21.0",
"@antelopejs/interface-api": "^0.2.0",
"@antelopejs/interface-core": "^0.4.0",
"@antelopejs/tooling-configs": "^0.3.3",
"@antelopejs/tooling-configs": "^0.4.0",
"@types/chai": "^4.3.20",
"@types/mocha": "^10.0.10",
"@types/node": "^22.19.15",
Expand Down
13 changes: 7 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading