@tineira/apc-conformance 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/http.js CHANGED
@@ -10,8 +10,8 @@
10
10
  * no credentials in URLs, every grant URL on the app's own origin, redirects never followed (a 3xx is a
11
11
  * failure), a byte cap on every body and a timeout on every request.
12
12
  */
13
- import { APC_JSON_MAX_BYTES, APC_JSON_PATH, APC_STANDARD_VERSION, CORE_TOOLS, REVOKED_HTTP_STATUS, compareStandard, declaresMcpUrl, isRevokedAnswer, parseApcManifest, } from "@tineira/apc-protocol";
14
- import { ConformanceError, ISO, attentionOf, checkCoreToolList, checkFocusShape, checkView, data, expect, isObject, resultKey, rpcBody } from "./shared.js";
13
+ import { APC_JSON_MAX_BYTES, APC_JSON_PATH, APC_STANDARD_VERSION, CORE_TOOLS, REVOKED_HTTP_STATUS, declaresMcpUrl, isRevokedAnswer, parseApcManifest, } from "@tineira/apc-protocol";
14
+ import { ConformanceError, ISO, attentionOf, checkCoreToolList, checkFocusShape, checkView, data, expect, isObject, resultKey, rpcBody, standardSupport } from "./shared.js";
15
15
  /**
16
16
  * The checks of the suite that need the person's page (an adapter in process), not reachable over HTTP:
17
17
  * `apcConformance(adapter)` runs them in the app's own tests.
@@ -267,12 +267,15 @@ function build(options) {
267
267
  },
268
268
  {
269
269
  id: "apc-json-standard",
270
- name: `its standard is one these checks know (0.5.0 to ${APC_STANDARD_VERSION})`,
270
+ name: `its standard is one these checks know (0.5.0 to ${APC_STANDARD_VERSION}, or a newer patch of it)`,
271
271
  async run() {
272
272
  if (!manifest)
273
273
  return "apc.json did not parse";
274
- expect(compareStandard(manifest.standard, "0.5.0") >= 0, `standard is ${manifest.standard}: apc.json exists since 0.5.0`);
275
- expect(compareStandard(manifest.standard, APC_STANDARD_VERSION) <= 0, `standard is ${manifest.standard}, newer than the ${APC_STANDARD_VERSION} these checks know: update @tineira/apc-conformance`);
274
+ const support = standardSupport(manifest.standard, APC_STANDARD_VERSION);
275
+ if (!support.ok)
276
+ throw new ConformanceError(support.reason);
277
+ if (support.note)
278
+ return { note: support.note };
276
279
  },
277
280
  },
278
281
  {
package/dist/index.d.ts CHANGED
@@ -99,7 +99,10 @@ export interface ApcAppAdapter {
99
99
  }
100
100
  export interface ConformanceCheck {
101
101
  name: string;
102
+ /** Throws when the check fails. */
102
103
  run(): Promise<void>;
104
+ /** Set by `run()` when the check passed with something worth saying (a newer patch of the standard, say). */
105
+ note?: string;
103
106
  }
104
107
  export interface ConformanceReport {
105
108
  app: string;
@@ -110,6 +113,7 @@ export interface ConformanceReport {
110
113
  name: string;
111
114
  ok: boolean;
112
115
  error?: string;
116
+ note?: string;
113
117
  }>;
114
118
  }
115
119
  /** The checks of the core, in order. Each makes its own agents, so any one can run alone. */
package/dist/index.js CHANGED
@@ -19,9 +19,21 @@
19
19
  * and a revoked grant's answer); the `apc-conformance` command (`cli.ts`) runs them from a terminal.
20
20
  */
21
21
  import { APC_AGENT_CLIENT_META, APC_JSON_MAX_BYTES, APC_JSON_PATH, APC_MOBILE_VIEWPORT, APC_STANDARD_VERSION, APC_LINK_KEY, APC_LINK_TOOL, APC_LINK_TTL_S, CORE_TOOLS, LINK_REFUSED_ERROR, REVOKED_ERROR, REVOKED_HTTP_STATUS, canonicalTarget, compareStandard, declaresMcpUrl, isRevokedAnswer, parseApcManifest, } from "@tineira/apc-protocol";
22
- import { ConformanceError, ISO, attentionOf, checkCoreToolList, checkFocusShape, checkView, data, expect, isObject, resultKey, rpcBody, text } from "./shared.js";
22
+ import { ConformanceError, ISO, attentionOf, checkCoreToolList, checkFocusShape, checkView, data, expect, isObject, resultKey, rpcBody, standardSupport, text } from "./shared.js";
23
23
  export { ConformanceError } from "./shared.js";
24
24
  export { HttpConformanceUsageError, IN_PROCESS_ONLY, httpConformance, redactGrantUrl, redactSecrets, runHttpConformance, } from "./http.js";
25
+ /** A check whose body may resolve to `{ note }`: the note lands on the check, and `run()` stays a test body. */
26
+ function noted(name, body) {
27
+ const check = {
28
+ name,
29
+ async run() {
30
+ const out = await body();
31
+ if (out?.note)
32
+ check.note = out.note;
33
+ },
34
+ };
35
+ return check;
36
+ }
25
37
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
26
38
  /** One JSON-RPC request over Streamable HTTP, stateless, as a dock's hub sends it: the HTTP status and the reply. */
27
39
  async function send(app, url, method, params) {
@@ -59,7 +71,7 @@ async function timed(work) {
59
71
  /** The checks of the core, in order. Each makes its own agents, so any one can run alone. */
60
72
  export function apcConformance(app) {
61
73
  const [first, second] = app.targets;
62
- const check = (name, run) => ({ name: `${app.name} (${app.profile}): ${name}`, run });
74
+ const check = (name, run) => noted(`${app.name} (${app.profile}): ${name}`, run);
63
75
  return [
64
76
  check("serves apc.json from its own origin, naming the profiles it serves and where its MCP URLs live", async () => {
65
77
  const { mcpUrl } = await app.connect();
@@ -80,7 +92,8 @@ export function apcConformance(app) {
80
92
  const parsed = parseApcManifest(body, origin);
81
93
  expect(parsed.ok, `${APC_JSON_PATH}: ${parsed.ok ? "" : `${parsed.error}, ${parsed.detail}`}`);
82
94
  const m = parsed.manifest;
83
- expect(compareStandard(m.standard, "0.5.0") >= 0 && compareStandard(m.standard, APC_STANDARD_VERSION) <= 0, `standard is ${m.standard}: apc.json exists since 0.5.0, and this suite checks up to ${APC_STANDARD_VERSION}`);
95
+ const support = standardSupport(m.standard, APC_STANDARD_VERSION);
96
+ expect(support.ok, support.ok ? "" : `${support.reason} (this suite checks 0.5.0 up to ${APC_STANDARD_VERSION}, and newer patches of it)`);
84
97
  expect(!m.profiles.includes("hub"), "an app's profiles are direct, dock or both, never hub");
85
98
  expect(m.profiles.includes(app.profile), `profiles ${JSON.stringify(m.profiles)} leave out ${app.profile}, which the app serves`);
86
99
  if (app.link)
@@ -88,6 +101,8 @@ export function apcConformance(app) {
88
101
  expect(declaresMcpUrl(m, mcpUrl), `the grant's MCP URL is not under ${origin}${m.mcp.path} (mcp.path)`);
89
102
  if (m.profiles.includes("direct"))
90
103
  expect(m.direct && new URL(m.direct.connect).origin === origin, "direct.connect is the app's own page");
104
+ if (support.note)
105
+ return { note: support.note };
91
106
  }),
92
107
  check("lists the core tools, with their arguments", async () => {
93
108
  const { mcpUrl } = await app.connect();
@@ -408,7 +423,7 @@ export async function runConformance(app) {
408
423
  for (const c of apcConformance(app)) {
409
424
  try {
410
425
  await c.run();
411
- results.push({ name: c.name, ok: true });
426
+ results.push({ name: c.name, ok: true, ...(c.note ? { note: c.note } : {}) });
412
427
  }
413
428
  catch (error) {
414
429
  results.push({ name: c.name, ok: false, error: error instanceof Error ? error.message : String(error) });
package/dist/shared.d.ts CHANGED
@@ -41,3 +41,16 @@ export declare function resultKey(r: ToolCallResult, key: string): {
41
41
  export declare function attentionOf(r: ToolCallResult): unknown;
42
42
  /** What `tools/list` must say about the core tools (shared by the suite and the CLI). */
43
43
  export declare function checkCoreToolList(listed: unknown, coreTools: readonly string[]): void;
44
+ /**
45
+ * Whether checks written for the standard `own` can judge an app that claims `claimed` in its apc.json:
46
+ * any version from 0.5.0 (apc.json exists since) up to `own`, and also a newer patch of the same minor
47
+ * (`own` 0.7.0, `claimed` 0.7.1), since a patch never changes the contract: that passes with a note. A newer
48
+ * minor or major may change the contract, so it fails: the checks need updating.
49
+ */
50
+ export declare function standardSupport(claimed: string, own: string): {
51
+ ok: true;
52
+ note?: string;
53
+ } | {
54
+ ok: false;
55
+ reason: string;
56
+ };
package/dist/shared.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * What the in-process suite (`index.ts`) and the HTTP checks (`http.ts`) share: the error a check
3
3
  * throws, how a JSON-RPC reply is read off Streamable HTTP, and the shapes of the core's view and focus.
4
4
  */
5
- import { parseParams, parseTarget } from "@tineira/apc-protocol";
5
+ import { compareStandard, parseParams, parseTarget } from "@tineira/apc-protocol";
6
6
  export class ConformanceError extends Error {
7
7
  }
8
8
  export function expect(condition, message) {
@@ -122,3 +122,23 @@ export function checkCoreToolList(listed, coreTools) {
122
122
  expect(timeout && (timeout.type === "integer" || timeout.type === "number") && timeout.minimum === 1, "wait_for_focus takes timeout_s, a whole number from 1");
123
123
  expect(!(wait.inputSchema?.required ?? []).includes("timeout_s"), "timeout_s is optional");
124
124
  }
125
+ /**
126
+ * Whether checks written for the standard `own` can judge an app that claims `claimed` in its apc.json:
127
+ * any version from 0.5.0 (apc.json exists since) up to `own`, and also a newer patch of the same minor
128
+ * (`own` 0.7.0, `claimed` 0.7.1), since a patch never changes the contract: that passes with a note. A newer
129
+ * minor or major may change the contract, so it fails: the checks need updating.
130
+ */
131
+ export function standardSupport(claimed, own) {
132
+ if (!/^\d+\.\d+\.\d+$/.test(claimed))
133
+ return { ok: false, reason: `standard is ${claimed}, not a version` };
134
+ if (compareStandard(claimed, "0.5.0") < 0)
135
+ return { ok: false, reason: `standard is ${claimed}: apc.json exists since 0.5.0` };
136
+ if (compareStandard(claimed, own) <= 0)
137
+ return { ok: true };
138
+ const [cMajor, cMinor] = claimed.split(".");
139
+ const [oMajor, oMinor] = own.split(".");
140
+ if (cMajor === oMajor && cMinor === oMinor) {
141
+ return { ok: true, note: `standard ${claimed} is a newer patch of the ${own} these checks know; a patch never changes the contract, so they apply (update @tineira/apc-conformance when you can)` };
142
+ }
143
+ return { ok: false, reason: `standard is ${claimed}, newer than the ${own} these checks know: update @tineira/apc-conformance` };
144
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tineira/apc-conformance",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Agent Pointed Context (APC) conformance: checks an app against the standard, in process with an adapter, or live over HTTP with the apc-conformance command.",
5
5
  "keywords": [
6
6
  "apc",
@@ -45,7 +45,7 @@
45
45
  "access": "public"
46
46
  },
47
47
  "dependencies": {
48
- "@tineira/apc-protocol": "^0.7.0"
48
+ "@tineira/apc-protocol": "^0.7.1"
49
49
  },
50
50
  "scripts": {},
51
51
  "types": "./dist/index.d.ts",