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
2 changes: 2 additions & 0 deletions .agents/skills/validate-sas-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Publication plans must reject empty/no-op targets and stale source/archive/run b

The admin review write must retain the verified administrator's authenticated client, pin the evaluated version and confirm the returned row's status, publication flag and reviewer. A closed dialog or successful HTTP transport alone is not publication evidence. Run the real Supabase HTTP transport regressions in `tests/admin-review-update.test.ts`; database denials, missing rows and mismatched versions/actors must reject. Confirm the actual persisted/public subset afterward.

After publishing the business cohort, run `npm run check:business-publication -- EVIDENCE_DIR HTTPS_ORIGIN NEW_REPORT_JSON`. It verifies all 21 anonymous metadata and plugin-manifest endpoints against the complete frozen plan, immutable registry prompt hashes, versions and exclusive supported model. It makes no model calls, mutations or automatic retries, rejects empty/no-op evidence and preserves its actual aggregate report. Repeat `npm run check:security` after publication: newly visible rows can reveal direct-read policies that were invisible when the tables contained only drafts. Keep the public-read allowlist unchanged; anonymous archive/evidence distribution uses the existing published-package server gates.

The original business case contract is pinned in `scripts/adversarial-approval.mjs`. Qualification, registry recording and publication planning require all 21 skills and 273 unchanged cases, including inputs, contexts, severities and expectations. Pilots never qualify. Test changes are not an allowed failure fix. Review each distinct raw response against the original domain expectation and record hash-bound decisions; group identical baseline evidence with the existing review-group command without automatically generating verdicts.

For interrupted local inference, reuse the worker's explicit `--resume` only when it accepts the original incomplete JSONL prefix and exact plan/runtime/package fingerprints. Preserve all prior rows, including errors. The worker requires a hash-bound attempt journal ending at a completed case, appends only not-yet-started cases and records its actual resumption. An in-flight or unknown attempt blocks recovery. Use `--max-cases N` for an explicit boundary checkpoint; exit 4 remains incomplete evidence. Changed or legacy runtime evidence must remain separate; never edit a raw output or its plan hash to make recovery pass. Run the deterministic recovery regressions with `python3 tests/worker_resume_test.py`.
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ Component artifacts are versioned and released independently via git tags:
- **`super-agent` CLI** — `cli-v*` tags publish cross-platform binaries.
- **`@superagentskill/sdk`** — versioned in `packages/sdk-ts/package.json`.

## [0.6.6] — 2026-10-24 — *Genesis* (alpha) — Restrict direct evidence reads

### Changed

- Require authentication for direct archive and compatibility table reads, preserving existing row predicates and public server/API distribution.
- Add a finite, source-bound verifier for all 21 public metadata and plugin-manifest endpoints without expanding the anonymous-read allowlist.

## [0.6.5] — 2026-10-24 — *Genesis* (alpha) — Authenticated publication repair

### Changed
Expand Down
7 changes: 7 additions & 0 deletions docs/adr/20261024-public-evidence-access.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Public evidence through package APIs

Direct anonymous reads of `package_artifacts` and `skill_compatibility` became visible when the first 21 qualified business versions were published. The unchanged anonymous-access check rejected both tables.

Restrict their existing SELECT policies to `authenticated`, retaining their current row predicates and administrator/author permissions. Do not expand the public-read allowlist. Anonymous package metadata and downloads continue through the existing server/API paths: `loadPluginPackage` requires a published, approved, free package and binds its version/archive; marketplace trust reads require a published, approved package.

Validate the migration with the transactional business RLS suite, including denial of direct anonymous current/old archive and compatibility reads. Then rerun anonymous coverage checks and verify all 21 public package APIs still return the qualified version, prompt hash and exclusive supported model. No adversarial criteria, source archives, publication status or reviewer may change.
4 changes: 2 additions & 2 deletions package-lock.json

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

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tanstack_start_ts",
"version": "0.6.5",
"version": "0.6.6",
"private": true,
"sideEffects": false,
"type": "module",
Expand Down Expand Up @@ -49,7 +49,8 @@
"check:business-skills": "node --experimental-strip-types scripts/build-business-skills.mjs --check && node scripts/check-business-skills.mjs",
"eval:business:local": "node --experimental-strip-types scripts/run-business-adversarial.mjs",
"eval:business:sql": "node scripts/record-business-adversarial.mjs",
"publish:business:sql": "node --experimental-strip-types scripts/prepare-business-publication.mjs"
"publish:business:sql": "node --experimental-strip-types scripts/prepare-business-publication.mjs",
"check:business-publication": "node scripts/check-business-publication.mjs"
},
"dependencies": {
"@ai-sdk/openai-compatible": "^2.0.47",
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "superagentskill",
"version": "0.6.5",
"version": "0.6.6",
"description": "Agent skills and candidate workflows from Super Agent Skill. Published registry packages expose available trust and execution evidence; candidate workflows include offline fixtures.",
"author": {
"name": "Super Agent Skill",
Expand Down
106 changes: 106 additions & 0 deletions scripts/check-business-publication.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/** Finite anonymous metadata verification; no models, authentication, writes or retries. */
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { createHash } from "node:crypto";
import { assertCompleteBusinessPlan } from "./adversarial-approval.mjs";
const [dir, base, output] = process.argv.slice(2);
if (process.argv.length !== 5 || !dir || !base || !output || existsSync(output))
throw new Error("Supply EVIDENCE_DIR HTTPS_ORIGIN NEW_REPORT_JSON");
const origin = new URL(base);
if (origin.protocol !== "https:" || origin.origin !== base || origin.username || origin.password)
throw new Error("An explicit HTTPS origin without credentials, query or path is required");
const plan = JSON.parse(readFileSync(`${dir}/plan.json`, "utf8"));
assertCompleteBusinessPlan(plan);
const registry = JSON.parse(readFileSync(`${dir}/registry.json`, "utf8"));
if (registry.length !== 21 || new Set(registry.map((r) => r.slug)).size !== 21)
throw new Error("Exactly 21 unique immutable registry bindings required");
const target = `${plan.model}/${plan.prompt_protocol}`;
for (const row of registry) {
const subject = plan.subjects.find((s) => s.slug === row.slug);
if (
!subject ||
subject.version !== row.latest_version ||
subject.prompt_sha256 !== row.prompt_sha256
)
throw new Error("Registry/plan source mismatch");
}
const results = new Array(registry.length);
let next = 0;
await Promise.all(
Array.from({ length: 3 }, async () => {
while (next < registry.length) {
const index = next++,
row = registry[index];
let status = null;
try {
const response = await fetch(
`${base}/api/public/packages/${encodeURIComponent(row.slug)}`,
{
headers: { accept: "application/json" },
redirect: "error",
signal: AbortSignal.timeout(15000),
},
);
status = response.status;
if (!response.ok) throw new Error(`HTTP ${status}`);
const data = await response.json();
const hash =
typeof data.system_prompt === "string"
? createHash("sha256").update(data.system_prompt).digest("hex")
: null;
const supported = Array.isArray(data.compatibility)
? data.compatibility.filter((c) => c.status === "supported").map((c) => c.runtime)
: [];
const pluginResponse = await fetch(
`${base}/api/public/plugins/${encodeURIComponent(row.slug)}/plugin.json`,
{
headers: { accept: "application/json" },
redirect: "error",
signal: AbortSignal.timeout(15000),
},
);
if (!pluginResponse.ok) throw new Error(`Plugin HTTP ${pluginResponse.status}`);
const plugin = await pluginResponse.json();
const passed =
data.slug === row.slug &&
data.version === row.latest_version &&
data.latest_version === row.latest_version &&
hash === row.prompt_sha256 &&
supported.length === 1 &&
supported[0] === target &&
plugin.name === row.slug &&
plugin.version === row.latest_version;
results[index] = {
slug: row.slug,
http_status: status,
version: data.version,
prompt_sha256: hash,
supported_runtimes: supported,
plugin_http_status: pluginResponse.status,
plugin_version: plugin.version,
passed,
};
} catch (error) {
results[index] = {
slug: row.slug,
http_status: status,
passed: false,
error: String(error),
};
}
}
}),
);
const report = {
verified_at: new Date().toISOString(),
base_url: base,
target,
total: results.length,
passed: results.filter((r) => r.passed).length,
model_calls: 0,
production_writes: 0,
automatic_retries: 0,
results,
};
writeFileSync(output, JSON.stringify(report, null, 2) + "\n", { flag: "wx", mode: 0o600 });
console.log(JSON.stringify({ total: report.total, passed: report.passed, report: output }));
process.exitCode = report.passed === 21 ? 0 : 1;
3 changes: 2 additions & 1 deletion scripts/test-business-rls.sql
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ select pg_temp.expect_error(format('insert into package_artifacts(version_id,pac
select pg_temp.expect_error(format('update public.packages set latest_version=''0.0.0-security-fixture'' where id=%L',current_setting('test.public_package')),'current version has no adversarial run','published version cannot switch to unevaluated source');
select set_config('request.jwt.claims','{}',true);
set local role anon;
select pg_temp.assert_true((select count(*)=1 from package_artifacts where version_id=current_setting('test.public_version')::uuid),'anonymous reader sees approved current archive');
select pg_temp.assert_true((select count(*)=0 from package_artifacts where version_id=current_setting('test.public_version')::uuid),'anonymous direct archive read denied; distribution uses gated public API');
select pg_temp.assert_true((select count(*)=0 from package_artifacts where version_id=current_setting('test.old_version')::uuid),'anonymous reader cannot see unreviewed version archive');
select pg_temp.assert_true((select count(*)=0 from skill_compatibility),'anonymous direct compatibility reads denied');
reset role;
select set_config('request.jwt.claims',jsonb_build_object('sub',current_setting('test.owner'),'email','owner@example.invalid','role','authenticated')::text,true);
set local role authenticated;
Expand Down
12 changes: 11 additions & 1 deletion src/lib/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* We are still pre-1.0: rapid evolution, no stability guarantees.
*/

export const PLATFORM_VERSION = "0.6.5" as const;
export const PLATFORM_VERSION = "0.6.6" as const;
export const PLATFORM_CODENAME = "Genesis" as const;
export const PLATFORM_STAGE: "alpha" | "beta" | "ga" = "alpha";
export const PLATFORM_BUILD_DATE = "2026-10-24" as const;
Expand All @@ -30,6 +30,16 @@ export type ChangelogEntry = {
* Every deployed change appends a new entry (or amends the top one before shipping).
*/
export const CHANGELOG: ChangelogEntry[] = [
{
version: "0.6.6",
date: "2026-10-24",
kind: "patch",
title: "Restrict direct evidence reads",
highlights: [
"Require authentication for direct archive and compatibility table reads.",
"Verify anonymous package APIs and downloads against all 21 qualified versions.",
],
},
{
version: "0.6.5",
date: "2026-10-24",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Anonymous distribution stays behind the published-package server/API gates.
-- Keep the existing row predicates and administrator/author privileges intact.
alter policy artifacts_read on public.package_artifacts to authenticated;
alter policy "compat public read" on public.skill_compatibility to authenticated;
58 changes: 58 additions & 0 deletions tests/business-publication-check.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, readFileSync, writeFileSync, existsSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { spawnSync } from "node:child_process";

test("publication verifier rejects an empty cohort before public API requests", () => {
const dir = mkdtempSync(join(tmpdir(), "sas-empty-publication-"));
try {
const model = JSON.parse(readFileSync("content/expansion/model-profile.json", "utf8")).model;
const prepare = spawnSync(
process.execPath,
[
"--experimental-strip-types",
"scripts/eval-business-adversarial.mjs",
"prepare",
model,
"--prompt-protocol",
"redact-route-generate-v1",
"--workdir",
dir,
],
{ encoding: "utf8" },
);
assert.equal(prepare.status, 0, prepare.stdout + prepare.stderr);
writeFileSync(join(dir, "registry.json"), "[]");
const output = join(dir, "result.json");
const run = spawnSync(
process.execPath,
["scripts/check-business-publication.mjs", dir, "https://not-contacted.invalid", output],
{ encoding: "utf8" },
);
assert.equal(run.status, 1);
assert.match(run.stderr, /Exactly 21 unique immutable registry bindings required/);
assert.equal(existsSync(output), false);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});

test("publication verifier preserves existing reports instead of overwriting evidence", () => {
const dir = mkdtempSync(join(tmpdir(), "sas-preserved-publication-"));
try {
const output = join(dir, "result.json");
writeFileSync(output, "original failed evidence");
const run = spawnSync(
process.execPath,
["scripts/check-business-publication.mjs", dir, "https://not-contacted.invalid", output],
{ encoding: "utf8" },
);
assert.equal(run.status, 1);
assert.match(run.stderr, /NEW_REPORT_JSON/);
assert.equal(readFileSync(output, "utf8"), "original failed evidence");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
Loading