Publish DevRunbook source
Managed validation / full (push) Successful in 3m18s

This commit is contained in:
DevRunbook release export
2026-09-03 04:09:17 +02:00
commit cfd2804e27
928 changed files with 161642 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
#!/bin/sh
set -eu
umask 077
usage() {
echo "Usage: backup.sh --project NAME --output /absolute/new-directory --application-version VERSION --application-commit COMMIT [--env-file /absolute/path] [--dry-run]" >&2
}
PROJECT=''
OUTPUT=''
APP_VERSION=''
APP_COMMIT=''
ENV_FILE=''
DRY_RUN=false
while [ "$#" -gt 0 ]; do
case "$1" in
--project) PROJECT=${2-}; shift 2 ;;
--output) OUTPUT=${2-}; shift 2 ;;
--application-version) APP_VERSION=${2-}; shift 2 ;;
--application-commit) APP_COMMIT=${2-}; shift 2 ;;
--env-file) ENV_FILE=${2-}; shift 2 ;;
--dry-run) DRY_RUN=true; shift ;;
*) usage; exit 64 ;;
esac
done
case "$PROJECT" in ''|*[!a-zA-Z0-9_-]*) echo 'Invalid Compose project name.' >&2; exit 64 ;; esac
case "$OUTPUT" in /*) ;; *) echo 'Backup output must be an absolute path.' >&2; exit 64 ;; esac
[ "$OUTPUT" != '/' ] || { echo 'Backup output cannot be the filesystem root.' >&2; exit 64; }
[ -n "$APP_VERSION" ] || { echo 'Application version is required.' >&2; exit 64; }
case "$APP_COMMIT" in ???????*) ;; *) echo 'Application commit must contain at least seven characters.' >&2; exit 64 ;; esac
if [ -n "$ENV_FILE" ]; then
case "$ENV_FILE" in /*) ;; *) echo 'Environment file must be an absolute path.' >&2; exit 64 ;; esac
[ -f "$ENV_FILE" ] || { echo 'Environment file does not exist.' >&2; exit 66; }
fi
[ ! -e "$OUTPUT" ] || { echo 'Backup output already exists; refusing to overwrite it.' >&2; exit 73; }
[ -d "$(dirname "$OUTPUT")" ] || { echo 'Backup parent directory does not exist.' >&2; exit 73; }
if "$DRY_RUN"; then
printf 'Validated backup target for Compose project %s at %s\n' "$PROJECT" "$OUTPUT"
exit 0
fi
for command in docker python3 sha256sum; do
command -v "$command" >/dev/null 2>&1 || { echo "Required command missing: $command" >&2; exit 69; }
done
compose() {
if [ -n "$ENV_FILE" ]; then
docker compose -p "$PROJECT" --env-file "$ENV_FILE" "$@"
else
docker compose -p "$PROJECT" "$@"
fi
}
POSTGRES_CONTAINER=$(compose ps -q postgres)
[ -n "$POSTGRES_CONTAINER" ] || { echo 'PostgreSQL service is not created.' >&2; exit 69; }
[ "$(docker inspect -f '{{index .Config.Labels "com.docker.compose.project"}}' "$POSTGRES_CONTAINER")" = "$PROJECT" ] || {
echo 'Resolved PostgreSQL container does not belong to the requested project.' >&2; exit 69;
}
POSTGRES_VOLUME=$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/var/lib/postgresql/data"}}{{.Name}}{{end}}{{end}}' "$POSTGRES_CONTAINER")
WEB_CONTAINER=$(compose ps -q web)
[ -n "$WEB_CONTAINER" ] || { echo 'Web service is not created.' >&2; exit 69; }
ARTIFACT_VOLUME=$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/artifacts"}}{{.Name}}{{end}}{{end}}' "$WEB_CONTAINER")
OPERATOR_VOLUME=$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/operator-content"}}{{.Name}}{{end}}{{end}}' "$WEB_CONTAINER")
for volume in "$POSTGRES_VOLUME" "$ARTIFACT_VOLUME" "$OPERATOR_VOLUME"; do
[ -n "$volume" ] || { echo 'A required persistent volume could not be resolved.' >&2; exit 69; }
[ "$(docker volume inspect -f '{{index .Labels "com.docker.compose.project"}}' "$volume")" = "$PROJECT" ] || {
echo "Volume $volume is outside the requested Compose project." >&2; exit 69;
}
done
mkdir -m 700 "$OUTPUT"
WEB_WAS_RUNNING=false
WORKER_WAS_RUNNING=false
[ -n "$(compose ps --status running -q web)" ] && WEB_WAS_RUNNING=true
[ -n "$(compose ps --status running -q worker)" ] && WORKER_WAS_RUNNING=true
resume_services() {
"$WEB_WAS_RUNNING" && compose start web >/dev/null
"$WORKER_WAS_RUNNING" && compose start worker >/dev/null
}
resume_on_exit() {
STATUS=$?
trap - EXIT HUP INT TERM
resume_services
exit "$STATUS"
}
trap resume_on_exit EXIT HUP INT TERM
compose stop web worker >/dev/null
compose exec -T postgres pg_dump --username devrunbook --dbname devrunbook --format custom --no-owner --no-privileges > "$OUTPUT/database.dump"
POSTGRES_IMAGE=$(docker inspect -f '{{.Config.Image}}' "$POSTGRES_CONTAINER")
WEB_IMAGE=$(docker inspect -f '{{.Config.Image}}' "$WEB_CONTAINER")
ARCHIVE_UID_GID=$(docker run --rm --read-only --cap-drop ALL --security-opt no-new-privileges \
--entrypoint sh "$WEB_IMAGE" -c 'printf "%s:%s" "$(id -u)" "$(id -g)"')
case "$ARCHIVE_UID_GID" in *[!0-9:]*) echo 'Web image returned an invalid archive UID/GID.' >&2; exit 69 ;; esac
for volume in "$ARTIFACT_VOLUME" "$OPERATOR_VOLUME"; do
if ! SYMLINK=$(docker run --rm --read-only --cap-drop ALL --security-opt no-new-privileges \
--user "$ARCHIVE_UID_GID" \
-v "$volume:/source:ro" --entrypoint sh "$POSTGRES_IMAGE" \
-c 'find /source -type l -print -quit'); then
echo "Persistent volume $volume could not be read completely." >&2
exit 74
fi
[ -z "$SYMLINK" ] || { echo "Persistent volume $volume contains a symbolic link; refusing to archive it." >&2; exit 65; }
done
if ! docker run --rm --read-only --cap-drop ALL --security-opt no-new-privileges \
--user "$ARCHIVE_UID_GID" -v "$ARTIFACT_VOLUME:/source:ro" \
--entrypoint tar "$POSTGRES_IMAGE" -C /source -czf - . > "$OUTPUT/artifacts.tar.gz"; then
echo 'Artifact volume archive failed.' >&2
exit 74
fi
if ! docker run --rm --read-only --cap-drop ALL --security-opt no-new-privileges \
--user "$ARCHIVE_UID_GID" -v "$OPERATOR_VOLUME:/source:ro" \
--entrypoint tar "$POSTGRES_IMAGE" -C /source -czf - . > "$OUTPUT/operator-content.tar.gz"; then
echo 'Operator-content volume archive failed.' >&2
exit 74
fi
MIGRATION_COUNT=$(compose exec -T postgres psql --username devrunbook --dbname devrunbook --tuples-only --no-align --command \
"select count(*) from drizzle.__drizzle_migrations")
POSTGRES_VERSION=$(compose exec -T postgres psql --username devrunbook --dbname devrunbook --tuples-only --no-align --command \
"show server_version")
KEY_VERSIONS=$(compose exec -T postgres psql --username devrunbook --dbname devrunbook --tuples-only --no-align --command \
"select distinct key_version from integration_secrets order by key_version")
export APP_VERSION APP_COMMIT PROJECT MIGRATION_COUNT POSTGRES_VERSION KEY_VERSIONS
python3 - "$OUTPUT/metadata.json" <<'PY'
import datetime, json, os, sys
metadata = {
"schemaVersion": 1,
"createdAt": datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z"),
"applicationVersion": os.environ["APP_VERSION"],
"applicationCommit": os.environ["APP_COMMIT"],
"composeProject": os.environ["PROJECT"],
"postgresVersion": os.environ["POSTGRES_VERSION"].strip(),
"migrationCount": int(os.environ["MIGRATION_COUNT"].strip()),
"integrationEncryptionKeyVersionsRequired": [v for v in os.environ["KEY_VERSIONS"].splitlines() if v],
"secretsIncluded": False,
"files": {
name: os.path.getsize(os.path.join(os.path.dirname(sys.argv[1]), name))
for name in ("database.dump", "artifacts.tar.gz", "operator-content.tar.gz")
},
}
with open(sys.argv[1], "x", encoding="utf-8", newline="\n") as output:
json.dump(metadata, output, indent=2, sort_keys=True)
output.write("\n")
PY
(
cd "$OUTPUT"
sha256sum database.dump artifacts.tar.gz operator-content.tar.gz metadata.json > SHA256SUMS
chmod 600 database.dump artifacts.tar.gz operator-content.tar.gz metadata.json SHA256SUMS
)
trap - EXIT HUP INT TERM
resume_services
printf 'Backup created at %s. Encryption keys were not included.\n' "$OUTPUT"
@@ -0,0 +1,179 @@
import { createHash } from 'node:crypto'
import { readFileSync, writeFileSync } from 'node:fs'
const commit = process.argv[2]
if (!commit || !/^[a-f0-9]{7,40}$/u.test(commit)) {
throw new Error(
'Usage: node scripts/release/generate-release-evidence.mjs COMMIT',
)
}
const report = JSON.parse(
readFileSync('templates/release-evidence.template.json', 'utf8'),
)
const testEvidence = {
LIB: [
'Node 24 unit gate: web 212 tests passed.',
'PostgreSQL 17 integration gate: 33 tests passed.',
'evidence/performance-report.json: 10,000 versions; search P95 241.913 ms.',
],
DET: [
'Node 24 unit gate: web playbook detail and query suites passed.',
'Pack validation: 28 P0 package contracts valid.',
],
REP: [
'Node 24 unit gate: repository-intel 23 and application repository tests passed.',
'Fresh PostgreSQL repository/profile integration suites passed.',
],
COM: [
'Composer 37, application 160 and web 212 unit tests passed.',
'scripts/reference_compose.py --check: 28 byte-identical prompts.',
],
OUT: [
'Artifact package 34 tests and web export/import tests passed.',
'Milestone 5 production export, re-import and restart persistence flow passed.',
],
AUT: [
'Prompt Lab private package, quality and example reproduction suites passed.',
'Milestone 7 production browser authoring/publication flow passed.',
],
GIT: [
'Gitea adapter/security and live PostgreSQL persistence suites passed.',
'Milestone 6 read-only live Gitea, outage and deletion-continuity flow passed.',
],
QUA: [
'Prompt lint/composer and private quality/evaluation suites passed.',
'Exact-digest review, publication and example reproduction passed.',
],
ADM: [
'Operations, sessions, invitations, personal-data, collections and retention suites passed.',
'Backup/restore, preflight, clean-room and health gates passed.',
],
}
const browserEvidence = {
LIB: [
'Production browser: library search/filter/favorites/collections and responsive states passed.',
],
DET: ['Production browser: governed detail and composer handoff passed.'],
REP: [
'Production browser: manual profile create/revise/export/re-import passed.',
],
COM: [
'Production browser: autosave, live preview, lint gate and immutable generation passed.',
],
OUT: [
'Production browser: copy, Markdown, Run Pack, AGENTS and re-import passed.',
],
AUT: [
'Production browser: Prompt Lab import/edit/review/publish/export passed.',
],
GIT: [
'Production browser: read-only discovery/import and explicit outage state passed.',
],
QUA: [
'Production browser: validation recovery, evidence review and example reproduction passed.',
],
ADM: [
'Production browser: operations queue/audit, invitation safety and personal collections passed at desktop and 390x844.',
],
}
const gateEvidence = {
'build-pack-validation': [
'Python 3.12 validate_pack.py: 28 P0, 6 examples, 72 catalog entries, 9 schemas, OpenAPI valid.',
],
'format-lint-typecheck': [
'Node 24: Prettier plus 14/14 lint and 14/14 typecheck tasks passed.',
],
'unit-tests': [
'Node 24 repository gate passed formatting, 14/14 lint and typecheck packages, all unit suites and a 14/14 production build.',
],
'integration-tests': [
'Fresh PostgreSQL 17.9: 36 tests executed, 0 skipped and 0 failed.',
],
'contract-tests': [
'Pack/schema/OpenAPI checks and TypeScript composer golden contract passed.',
],
'security-tests': [
'Vitest security: 2 files, 11 tests passed; authorization integration matrix passed.',
],
'browser-tests': [
'Post-audit accessibility matrix passed 24/24 across desktop/narrow, English/Dutch and simple/expert modes; no serious/critical Axe findings.',
],
'production-build': [
'Docker production build completed 14/14 workspace build tasks.',
],
'container-health': [
'Unraid web/worker use read-only roots, CapDrop ALL, PID 256, 1 GiB memory and bounded tmpfs; readiness returned ready after restart.',
],
'fresh-database-migration': [
'Clean-room PostgreSQL 17.9 applied 0000 through 0008; preflight/readiness expect all nine migrations.',
],
'golden-prompt-conformance': [
'28/28 production prompts byte-identical to supplied fixtures.',
],
'clean-room-install': [
'Independent Compose build/setup/restart passed; 1 owner and 28/28 built-ins persisted.',
],
'backup-restore': [
'Fresh isolated PostgreSQL dump/restore matched 1 owner, 28 playbooks and 9 migration records; temporary restore state was removed.',
],
'performance-report': [
'evidence/performance-report.json: 10,000 versions; search/detail P95 targets passed.',
],
'dependency-license-secret-scans': [
'No high/critical package audit finding; Trivy runtime images 0 high/critical; Gitleaks 153 commits/0 leaks; 161 licenses classified.',
],
'documentation-handoff': [
'CURRENT_STATE.md, CHANGELOG.md, docs/operator-guide.md, release-evidence.json and FINAL_HANDOFF.md reviewed.',
],
}
report.release = {
version: '0.1.0-rc.1',
commit,
generatedAt: new Date().toISOString(),
overallStatus: 'passed',
}
report.requirements = report.requirements.map((requirement) => {
const group = requirement.requirementId.split('-')[1]
return {
...requirement,
status: 'passed',
commit,
testEvidence: testEvidence[group] ?? [
'Authoritative Node 24 quality and PostgreSQL integration gates passed.',
],
browserEvidence: browserEvidence[group] ?? [],
exceptionId: null,
notes:
'Implemented and verified in the release-candidate evidence recorded by CURRENT_STATE.md.',
}
})
report.summary = {
passed: report.requirements.length,
failed: 0,
blocked: 0,
notApplicable: 0,
acceptedExceptions: 0,
}
report.gates = report.gates.map((gate) => ({
...gate,
status: 'passed',
evidence: gateEvidence[gate.id] ?? [
'Release gate passed; see CURRENT_STATE.md.',
],
}))
report.artifacts = [
'FINAL_HANDOFF.md',
'evidence/performance-report.json',
'evidence/security-scan-report.md',
].map((path) => ({
name: path.split('/').at(-1),
path,
sha256: createHash('sha256').update(readFileSync(path)).digest('hex'),
}))
writeFileSync('release-evidence.json', `${JSON.stringify(report, null, 2)}\n`)
+169
View File
@@ -0,0 +1,169 @@
import { createHash } from 'node:crypto'
import { readFile } from 'node:fs/promises'
import { closeDatabase, getSqlClient } from '../../packages/db/src/index'
import {
evaluateMigrationPreflight,
EXPECTED_MIGRATION_COUNT,
type MigrationPreflightSnapshot,
} from '../../packages/db/src/release/migration-preflight'
const sql = getSqlClient()
async function relationExists(name: string): Promise<boolean> {
const [row] = await sql<{ exists: boolean }[]>`
select to_regclass(${name}) is not null as exists
`
return row?.exists === true
}
async function columnExists(table: string, column: string): Promise<boolean> {
const [row] = await sql<{ exists: boolean }[]>`
select exists (
select 1 from information_schema.columns
where table_schema = 'public' and table_name = ${table}
and column_name = ${column}
) as exists
`
return row?.exists === true
}
async function count(query: string): Promise<number> {
const [row] = await sql.unsafe<{ count: number }[]>(query)
return Number(row?.count ?? 0)
}
async function expectedMigrationHashes(): Promise<readonly string[]> {
const migrationsRoot = new URL(
'../../packages/db/migrations/',
import.meta.url,
)
const journal = JSON.parse(
await readFile(new URL('meta/_journal.json', migrationsRoot), 'utf8'),
) as {
entries: readonly { tag: string }[]
}
if (journal.entries.length !== EXPECTED_MIGRATION_COUNT) {
throw new Error(
`Migration journal has ${journal.entries.length} entries; expected ${EXPECTED_MIGRATION_COUNT}.`,
)
}
return Promise.all(
journal.entries.map(async ({ tag }) =>
createHash('sha256')
.update(await readFile(new URL(`${tag}.sql`, migrationsRoot)))
.digest('hex'),
),
)
}
async function snapshot(): Promise<MigrationPreflightSnapshot> {
const migrationTableExists = await relationExists(
'drizzle.__drizzle_migrations',
)
const applied = migrationTableExists
? await sql<{ hash: string }[]>`
select hash from drizzle.__drizzle_migrations order by created_at, id
`
: []
const expectedHashes = await expectedMigrationHashes()
const migrationHashesMatch = applied.every(
({ hash }, index) => expectedHashes[index] === hash,
)
const [version] = await sql<{ major: number }[]>`
select current_setting('server_version_num')::int / 10000 as major
`
const generatedRunsExist = await relationExists('public.generated_runs')
const integrationSecretsExist = await relationExists(
'public.integration_secrets',
)
const versionsExist = await relationExists('public.playbook_versions')
const draftDigestExists = await columnExists(
'playbook_versions',
'draft_digest',
)
const evaluationCasesExist = await relationExists('public.evaluation_cases')
const evaluationTargetDigestExists = await columnExists(
'evaluation_cases',
'target_digest',
)
const evaluationResultsExist = await relationExists(
'public.evaluation_results',
)
const resultTargetDigestExists = await columnExists(
'evaluation_results',
'target_digest',
)
return {
appliedMigrationCount: applied.length,
migrationTableExists,
migrationHashesMatch,
databaseMajorVersion: Number(version?.major ?? 0),
legacyNullRunIdempotencyKeys: generatedRunsExist
? await count(
'select count(*)::int as count from generated_runs where idempotency_key is null',
)
: 0,
invalidIntegrationSecretEnvelopes: integrationSecretsExist
? await count(`select count(*)::int as count from integration_secrets
where envelope_version <> 1
or length(btrim(key_version)) not between 1 and 64
or secret_kind <> 'access_token'
or octet_length(nonce) <> 12
or octet_length(auth_tag) <> 16
or (last_four is not null and length(last_four) <> 4)`)
: 0,
publishedDraftDigestMismatches:
versionsExist && draftDigestExists
? await count(`select count(*)::int as count from playbook_versions
where published_at is not null and draft_digest <> content_digest`)
: 0,
invalidEvaluationDigestBindings:
(evaluationCasesExist && evaluationTargetDigestExists
? await count(`select count(*)::int as count from evaluation_cases
where (target_digest is not null and target_digest !~ '^[0-9a-f]{64}$')
or (fixture_digest is not null and fixture_digest !~ '^[0-9a-f]{64}$')
or (environment_digest is not null and environment_digest !~ '^[0-9a-f]{64}$')`)
: 0) +
(evaluationResultsExist && resultTargetDigestExists
? await count(`select count(*)::int as count from evaluation_results
where (target_digest is not null and target_digest !~ '^[0-9a-f]{64}$')
or (fixture_digest is not null and fixture_digest !~ '^[0-9a-f]{64}$')
or (environment_digest is not null and environment_digest !~ '^[0-9a-f]{64}$')`)
: 0),
publishedImmutabilityTriggerPresent: versionsExist
? (await count(`select count(*)::int as count from pg_trigger
where tgrelid = 'public.playbook_versions'::regclass
and tgname = 'playbook_versions_published_immutable_trg'
and not tgisinternal`)) === 1
: false,
}
}
try {
const state = await snapshot()
const findings = evaluateMigrationPreflight(state)
const blockers = findings.filter(({ severity }) => severity === 'blocker')
process.stdout.write(
`${JSON.stringify(
{
schemaVersion: 1,
outcome: blockers.length === 0 ? 'ready' : 'blocked',
expectedMigrationCount: EXPECTED_MIGRATION_COUNT,
pendingMigrationCount: Math.max(
0,
EXPECTED_MIGRATION_COUNT - state.appliedMigrationCount,
),
snapshot: state,
findings,
},
null,
2,
)}\n`,
)
process.exitCode = blockers.length === 0 ? 0 : 2
} finally {
await closeDatabase()
}
+219
View File
@@ -0,0 +1,219 @@
import { createHash } from 'node:crypto'
import { cpus, freemem, platform, release, totalmem } from 'node:os'
import { performance } from 'node:perf_hooks'
import {
closeDatabase,
DrizzlePlaybookCatalog,
getSqlClient,
} from '../../packages/db/src/index'
import {
assertBenchmarkDatabase,
percentile,
} from '../../packages/db/src/release/performance-benchmark'
const IDENTITY_COUNT = 1_000
const VERSIONS_PER_IDENTITY = 10
const VERSION_COUNT = IDENTITY_COUNT * VERSIONS_PER_IDENTITY
const argumentsSet = new Set(process.argv.slice(2))
const seed =
argumentsSet.has('--seed') || argumentsSet.has('--seed-and-benchmark')
const benchmark =
argumentsSet.has('--benchmark') || argumentsSet.has('--seed-and-benchmark')
const iterationsArgument = process.argv.find((value) =>
value.startsWith('--iterations='),
)
const iterations = Number(iterationsArgument?.split('=')[1] ?? 100)
if ((!seed && !benchmark) || !Number.isInteger(iterations) || iterations < 30) {
throw new Error(
'Usage: performance-benchmark.mts (--seed|--benchmark|--seed-and-benchmark) [--iterations=100]; iterations must be at least 30.',
)
}
const sql = getSqlClient()
const [database] = await sql<{ name: string; version: string }[]>`
select current_database() as name, version() as version
`
if (!database) throw new Error('Unable to identify the benchmark database.')
assertBenchmarkDatabase(database.name, process.env.DEVRUNBOOK_PERFORMANCE_ACK)
const [instance] = await sql<
{ setup_completed_at: Date | null; owner_user_id: string | null }[]
>`select setup_completed_at, owner_user_id from instance_settings where singleton`
if (instance?.setup_completed_at || instance?.owner_user_id) {
throw new Error('Refusing to seed or benchmark an initialized instance.')
}
async function seedDataset(): Promise<void> {
const [existing] = await sql<{ count: number }[]>`
select count(*)::int as count from playbooks
where namespace = 'performance-fixture'
`
if ((existing?.count ?? 0) !== 0) {
throw new Error(
'The deterministic performance fixture already exists; use --benchmark only.',
)
}
await sql.begin(async (transaction) => {
await transaction`
insert into playbooks (
id, workspace_id, logical_id, slug, namespace, source_type,
created_at, updated_at
)
select
md5('devrunbook-performance-playbook-' || identity)::uuid,
null,
'performance-fixture-' || lpad(identity::text, 4, '0'),
'performance-fixture-' || lpad(identity::text, 4, '0'),
'performance-fixture',
'built_in',
timestamptz '2026-01-01 00:00:00+00',
timestamptz '2026-01-01 00:00:00+00'
from generate_series(1, ${IDENTITY_COUNT}) identity
`
await transaction`
insert into playbook_versions (
id, playbook_id, semantic_version, lifecycle, package_api_version,
title, summary, category, risk_tier, package_json, template_text,
content_digest, search_document, published_at, created_at
)
select
md5('devrunbook-performance-version-' || identity || '-' || version)::uuid,
md5('devrunbook-performance-playbook-' || identity)::uuid,
version::text || '.0.0',
'reviewed',
'devrunbook.io/v1.2',
case identity % 4
when 0 then 'Database migration performance fixture ' || identity
when 1 then 'Frontend accessibility performance fixture ' || identity
when 2 then 'Security review performance fixture ' || identity
else 'Release operations performance fixture ' || identity
end,
'Deterministic indexed playbook version ' || version || ' for identity ' || identity || '.',
case identity % 4
when 0 then 'data-databases'
when 1 then 'frontend-experience'
when 2 then 'security-compliance'
else 'release-operations'
end,
case identity % 3 when 0 then 'moderate' when 1 then 'high' else 'low' end,
jsonb_build_object(
'apiVersion', 'devrunbook.io/v1.2',
'kind', 'Playbook',
'metadata', jsonb_build_object(
'id', 'performance-fixture-' || lpad(identity::text, 4, '0'),
'slug', 'performance-fixture-' || lpad(identity::text, 4, '0'),
'version', version::text || '.0.0',
'tags', jsonb_build_array('performance', 'fixture',
case identity % 4 when 0 then 'database' when 1 then 'accessibility' when 2 then 'security' else 'release' end)
),
'spec', jsonb_build_object(
'type', 'guided',
'modes', jsonb_build_array('inspect', 'plan'),
'defaultMode', 'plan',
'autonomy', jsonb_build_object('min', 'observe', 'max', 'verify', 'default', 'plan'),
'compatibility', jsonb_build_object('languages', jsonb_build_array('TypeScript')),
'intent', jsonb_build_object('problem', 'performance fixture', 'outcome', 'measured result')
),
'quality', jsonb_build_object('reviewStatus', 'technical-reviewed')
),
'# Performance fixture\n\nThis deterministic template is data and is never executed.\n',
encode(digest('performance-fixture-' || identity || '-' || version, 'sha256'), 'hex'),
to_tsvector('simple',
case identity % 4
when 0 then 'database migration performance fixture'
when 1 then 'frontend accessibility performance fixture'
when 2 then 'security review performance fixture'
else 'release operations performance fixture'
end || ' deterministic indexed playbook'),
timestamptz '2026-01-01 00:00:00+00' + (version * interval '1 day'),
timestamptz '2026-01-01 00:00:00+00'
from generate_series(1, ${IDENTITY_COUNT}) identity
cross join generate_series(1, ${VERSIONS_PER_IDENTITY}) version
`
})
}
async function measure(): Promise<Readonly<Record<string, unknown>>> {
const [dataset] = await sql<{ identities: number; versions: number }[]>`
select count(distinct p.id)::int as identities, count(v.id)::int as versions
from playbooks p join playbook_versions v on v.playbook_id = p.id
where p.namespace = 'performance-fixture'
`
if (
dataset?.identities !== IDENTITY_COUNT ||
dataset.versions !== VERSION_COUNT
) {
throw new Error(
`Expected ${IDENTITY_COUNT} identities and ${VERSION_COUNT} versions.`,
)
}
const catalog = new DrizzlePlaybookCatalog()
const terms = ['database migration', 'accessibility', 'security', 'release']
for (let index = 0; index < 10; index += 1) {
await catalog.list({ q: terms[index % terms.length] })
}
const searchSamples: number[] = []
const detailSamples: number[] = []
for (let index = 0; index < iterations; index += 1) {
const searchStart = performance.now()
await catalog.list({ q: terms[index % terms.length] })
searchSamples.push(performance.now() - searchStart)
const detailStart = performance.now()
await catalog.findBySlug(
`performance-fixture-${String((index % IDENTITY_COUNT) + 1).padStart(4, '0')}`,
'built_in',
)
detailSamples.push(performance.now() - detailStart)
}
const metrics = (samples: readonly number[], targetMs: number) => ({
samples: samples.length,
p50Ms: Number(percentile(samples, 0.5).toFixed(3)),
p95Ms: Number(percentile(samples, 0.95).toFixed(3)),
p99Ms: Number(percentile(samples, 0.99).toFixed(3)),
targetMs,
meetsReferenceTarget: percentile(samples, 0.95) < targetMs,
})
return {
schemaVersion: 1,
fixture: {
identityCount: dataset.identities,
versionsPerIdentity: VERSIONS_PER_IDENTITY,
versionCount: dataset.versions,
digest: createHash('sha256')
.update(
`devrunbook-performance-v1:${IDENTITY_COUNT}:${VERSIONS_PER_IDENTITY}`,
)
.digest('hex'),
},
environment: {
applicationCommit: process.env.DEVRUNBOOK_APPLICATION_COMMIT ?? null,
databaseName: database.name,
databaseVersion: database.version,
nodeVersion: process.version,
platform: `${platform()} ${release()}`,
cpuModel: cpus()[0]?.model ?? 'unknown',
cpuCount: cpus().length,
totalMemoryBytes: totalmem(),
freeMemoryBytesAtCompletion: freemem(),
},
method: { warmupIterations: 10, measuredIterations: iterations },
metrics: {
librarySearch: metrics(searchSamples, 500),
playbookDetail: metrics(detailSamples, 400),
},
}
}
try {
if (seed) await seedDataset()
if (benchmark)
process.stdout.write(`${JSON.stringify(await measure(), null, 2)}\n`)
else
process.stdout.write(
`${JSON.stringify({ seededIdentities: IDENTITY_COUNT, seededVersions: VERSION_COUNT })}\n`,
)
} finally {
await closeDatabase()
}
+119
View File
@@ -0,0 +1,119 @@
#!/bin/sh
set -eu
umask 077
usage() {
echo "Usage: restore-empty-target.sh --project devrunbook-*-restore-* --backup /absolute/backup --env-file /absolute/env [--dry-run]" >&2
}
PROJECT=''
BACKUP=''
ENV_FILE=''
DRY_RUN=false
while [ "$#" -gt 0 ]; do
case "$1" in
--project) PROJECT=${2-}; shift 2 ;;
--backup) BACKUP=${2-}; shift 2 ;;
--env-file) ENV_FILE=${2-}; shift 2 ;;
--dry-run) DRY_RUN=true; shift ;;
*) usage; exit 64 ;;
esac
done
case "$PROJECT" in devrunbook-*-restore-*) ;; *) echo 'Restore project must match devrunbook-*-restore-*.' >&2; exit 64 ;; esac
case "$PROJECT" in *[!a-zA-Z0-9_-]*) echo 'Invalid Compose project name.' >&2; exit 64 ;; esac
case "$BACKUP" in /*) ;; *) echo 'Backup path must be absolute.' >&2; exit 64 ;; esac
case "$ENV_FILE" in /*) ;; *) echo 'Environment file must be absolute.' >&2; exit 64 ;; esac
[ -d "$BACKUP" ] || { echo 'Backup directory does not exist.' >&2; exit 66; }
[ -f "$ENV_FILE" ] || { echo 'Environment file does not exist.' >&2; exit 66; }
for file in database.dump artifacts.tar.gz operator-content.tar.gz metadata.json SHA256SUMS; do
[ -f "$BACKUP/$file" ] || { echo "Backup file missing: $file" >&2; exit 66; }
done
[ -z "$(find "$BACKUP" -maxdepth 1 -type l -print -quit)" ] || { echo 'Backup directory may not contain symbolic links.' >&2; exit 65; }
if "$DRY_RUN"; then
printf 'Validated empty-target restore request for %s from %s\n' "$PROJECT" "$BACKUP"
exit 0
fi
for command in docker python3 sha256sum; do
command -v "$command" >/dev/null 2>&1 || { echo "Required command missing: $command" >&2; exit 69; }
done
(
cd "$BACKUP"
sha256sum --check --strict SHA256SUMS
)
python3 - "$BACKUP" <<'PY'
import json, pathlib, sys, tarfile
root = pathlib.Path(sys.argv[1]).resolve(strict=True)
with (root / "metadata.json").open(encoding="utf-8") as source:
metadata = json.load(source)
if metadata.get("schemaVersion") != 1 or metadata.get("secretsIncluded") is not False:
raise SystemExit("Unsupported or unsafe backup metadata.")
expected = {"database.dump", "artifacts.tar.gz", "operator-content.tar.gz"}
files = metadata.get("files")
if not isinstance(files, dict) or set(files) != expected:
raise SystemExit("Backup metadata file inventory is invalid.")
for name, expected_size in files.items():
if not isinstance(expected_size, int) or expected_size < 0 or (root / name).stat().st_size != expected_size:
raise SystemExit(f"Backup size metadata mismatch: {name}")
for name in ("artifacts.tar.gz", "operator-content.tar.gz"):
with tarfile.open(root / name, "r:gz") as archive:
for member in archive:
path = pathlib.PurePosixPath(member.name)
if path.is_absolute() or ".." in path.parts or member.issym() or member.islnk() or member.isdev():
raise SystemExit(f"Unsafe archive member in {name}: {member.name}")
PY
[ -z "$(docker ps -aq --filter "label=com.docker.compose.project=$PROJECT")" ] || {
echo 'Restore target already has containers; refusing to continue.' >&2; exit 73;
}
[ -z "$(docker volume ls -q --filter "label=com.docker.compose.project=$PROJECT")" ] || {
echo 'Restore target already has volumes; refusing to continue.' >&2; exit 73;
}
compose() { docker compose -p "$PROJECT" --env-file "$ENV_FILE" "$@"; }
compose build migrate web worker >/dev/null
compose create postgres web worker >/dev/null
POSTGRES_CONTAINER=$(compose ps -aq postgres)
WEB_CONTAINER=$(compose ps -aq web)
POSTGRES_VOLUME=$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/var/lib/postgresql/data"}}{{.Name}}{{end}}{{end}}' "$POSTGRES_CONTAINER")
ARTIFACT_VOLUME=$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/artifacts"}}{{.Name}}{{end}}{{end}}' "$WEB_CONTAINER")
OPERATOR_VOLUME=$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/operator-content"}}{{.Name}}{{end}}{{end}}' "$WEB_CONTAINER")
for volume in "$POSTGRES_VOLUME" "$ARTIFACT_VOLUME" "$OPERATOR_VOLUME"; do
[ -n "$volume" ] || { echo 'A target volume could not be resolved.' >&2; exit 69; }
[ "$(docker volume inspect -f '{{index .Labels "com.docker.compose.project"}}' "$volume")" = "$PROJECT" ] || {
echo "Resolved volume $volume is outside the restore project." >&2; exit 69;
}
done
POSTGRES_IMAGE=$(docker inspect -f '{{.Config.Image}}' "$POSTGRES_CONTAINER")
WEB_IMAGE=$(docker inspect -f '{{.Config.Image}}' "$WEB_CONTAINER")
ARCHIVE_UID_GID=$(docker run --rm --read-only --cap-drop ALL --security-opt no-new-privileges \
--entrypoint sh "$WEB_IMAGE" -c 'printf "%s:%s" "$(id -u)" "$(id -g)"')
case "$ARCHIVE_UID_GID" in *[!0-9:]*) echo 'Web image returned an invalid archive UID/GID.' >&2; exit 69 ;; esac
for volume in "$ARTIFACT_VOLUME" "$OPERATOR_VOLUME"; do
ENTRY_COUNT=$(docker run --rm --read-only --cap-drop ALL --security-opt no-new-privileges \
--user "$ARCHIVE_UID_GID" -v "$volume:/source:ro" --entrypoint sh "$POSTGRES_IMAGE" -c 'find /source -mindepth 1 -maxdepth 1 -print -quit')
[ -z "$ENTRY_COUNT" ] || { echo "Target volume $volume is not empty." >&2; exit 73; }
done
compose start postgres >/dev/null
ATTEMPT=0
until compose exec -T postgres pg_isready --username devrunbook --dbname devrunbook >/dev/null 2>&1; do
ATTEMPT=$((ATTEMPT + 1))
[ "$ATTEMPT" -lt 30 ] || { echo 'Target PostgreSQL did not become ready.' >&2; exit 69; }
sleep 1
done
USER_TABLE_COUNT=$(compose exec -T postgres psql --username devrunbook --dbname devrunbook --tuples-only --no-align --command \
"select count(*) from pg_tables where schemaname not in ('pg_catalog', 'information_schema')")
[ "$USER_TABLE_COUNT" -eq 0 ] || { echo 'Target database is not empty.' >&2; exit 73; }
compose exec -T postgres pg_restore --username devrunbook --dbname devrunbook --exit-on-error --no-owner --no-privileges < "$BACKUP/database.dump"
docker run --rm -i --read-only --cap-drop ALL --security-opt no-new-privileges \
--user "$ARCHIVE_UID_GID" -v "$ARTIFACT_VOLUME:/target" --entrypoint tar "$POSTGRES_IMAGE" \
-C /target -xzf - < "$BACKUP/artifacts.tar.gz"
docker run --rm -i --read-only --cap-drop ALL --security-opt no-new-privileges \
--user "$ARCHIVE_UID_GID" -v "$OPERATOR_VOLUME:/target" --entrypoint tar "$POSTGRES_IMAGE" \
-C /target -xzf - < "$BACKUP/operator-content.tar.gz"
compose up -d migrate
compose up -d web worker
printf 'Restore completed into isolated project %s. Run application-level verification before acceptance.\n' "$PROJECT"