forge-workflow 0.1.0-beta.5 → 0.1.0-beta.7
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/AGENTS.md +4 -0
- package/CHANGELOG.md +48 -0
- package/CLAUDE.md +0 -12
- package/CODING_STANDARDS.md +72 -0
- package/bin/forge.js +12 -1
- package/docs/guides/MIGRATION.md +3 -3
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
- package/docs/reference/INSTALL.md +4 -0
- package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
- package/docs/reference/RELEASE.md +4 -4
- package/docs/reference/github-accounts.md +134 -0
- package/docs/reference/shepherd.md +63 -13
- package/lib/adapters/pr-state-adapter.js +15 -2
- package/lib/base-remote.js +138 -0
- package/lib/beta5-compatibility-evidence.js +1093 -0
- package/lib/bun-lockfile-proof.js +413 -0
- package/lib/bun-workflow-pins.js +461 -0
- package/lib/capabilities/index.js +9 -0
- package/lib/capabilities/model.js +141 -0
- package/lib/capabilities/probes.js +347 -0
- package/lib/codex-skills.js +2 -2
- package/lib/commands/_manifest.js +1 -0
- package/lib/commands/_registry.js +48 -18
- package/lib/commands/clean.js +57 -1
- package/lib/commands/doctor.js +37 -6
- package/lib/commands/gate.js +197 -27
- package/lib/commands/github.js +215 -0
- package/lib/commands/hooks.js +54 -6
- package/lib/commands/memory.js +66 -2
- package/lib/commands/merge.js +720 -73
- package/lib/commands/plan.js +33 -2
- package/lib/commands/pr.js +2 -0
- package/lib/commands/preflight.js +10 -2
- package/lib/commands/push.js +120 -9
- package/lib/commands/recall.js +95 -61
- package/lib/commands/release.js +23 -2
- package/lib/commands/remember.js +28 -4
- package/lib/commands/serve.js +26 -9
- package/lib/commands/setup.js +132 -4
- package/lib/commands/shepherd.js +578 -72
- package/lib/commands/ship.js +15 -69
- package/lib/commands/skill.js +8 -0
- package/lib/commands/team.js +47 -8
- package/lib/commands/test.js +163 -4
- package/lib/commands/validate.js +78 -22
- package/lib/commands/worktree.js +155 -19
- package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
- package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
- package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
- package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
- package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
- package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
- package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
- package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
- package/lib/forge-issues.js +78 -0
- package/lib/gate-events.js +98 -10
- package/lib/github-context.js +308 -0
- package/lib/global-flags.js +1 -0
- package/lib/hook-renderer.js +29 -1
- package/lib/issue-render.js +19 -0
- package/lib/kernel/broker.js +723 -31
- package/lib/kernel/claim-reconciler.js +238 -0
- package/lib/kernel/lease-enforcer.js +9 -4
- package/lib/kernel/legacy-claim-repair.js +442 -0
- package/lib/kernel/live-claim-projection.js +26 -0
- package/lib/kernel/migrations.js +118 -3
- package/lib/kernel/readiness-model.js +184 -12
- package/lib/kernel/schema.js +49 -1
- package/lib/kernel/sqlite-driver.js +3322 -183
- package/lib/kernel/taxonomy-validator.js +4 -1
- package/lib/kernel/windows-private-acl.js +239 -0
- package/lib/memory/hygiene.js +191 -0
- package/lib/memory/router.js +94 -27
- package/lib/memory/usage-evidence.js +4 -0
- package/lib/memory-digest.js +59 -0
- package/lib/merge-rules.js +135 -17
- package/lib/npm-publish-workflow.js +233 -40
- package/lib/package-root.js +2 -0
- package/lib/pr-monitor/auto-actions.js +169 -28
- package/lib/pr-monitor/differ.js +110 -4
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/flow-monitor.js +1424 -0
- package/lib/pr-monitor/gather.js +251 -44
- package/lib/pr-monitor/journal.js +0 -37
- package/lib/pr-monitor/monitor.js +117 -10
- package/lib/pr-monitor/process-identity.js +117 -0
- package/lib/pr-monitor/reconcile-executor.js +1101 -625
- package/lib/pr-monitor/reconcile.js +0 -0
- package/lib/pr-monitor/render-summary.js +121 -24
- package/lib/pr-monitor/review-preflight.js +269 -0
- package/lib/pr-monitor/shepherd-lease.js +28 -19
- package/lib/pr-monitor/verdict.js +438 -0
- package/lib/pr-monitor/watch-lifecycle.js +144 -38
- package/lib/pr-monitor/watch-owner.js +1414 -0
- package/lib/pr-monitor/watch.js +129 -58
- package/lib/pr-shepherd.js +17 -3
- package/lib/project-memory.js +145 -3
- package/lib/protected-state-authority.js +799 -4
- package/lib/protected-state-surfaces.js +181 -3
- package/lib/release-readiness.js +2 -3
- package/lib/review-adapter.js +65 -0
- package/lib/skills-sync.js +65 -32
- package/lib/validation/risk-manifest.js +339 -0
- package/lib/validation-receipt.js +190 -0
- package/lib/workflow/enforce-stage.js +44 -0
- package/lib/workflow/plan-authority.js +225 -0
- package/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/node_modules/@forge/contracts/index.js +32 -0
- package/node_modules/@forge/contracts/package.json +35 -0
- package/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/node_modules/@forge/contracts/src/identity.js +42 -0
- package/node_modules/@forge/contracts/src/schema.js +72 -0
- package/node_modules/@forge/contracts/src/validate.js +305 -0
- package/node_modules/@forge/flow/index.js +114 -0
- package/node_modules/@forge/flow/package.json +34 -0
- package/node_modules/@forge/flow/src/bounded-loop.js +415 -0
- package/node_modules/@forge/flow/src/efficiency-supervisor.js +89 -0
- package/node_modules/@forge/flow/src/executor.js +279 -0
- package/node_modules/@forge/flow/src/monitor-durability.js +419 -0
- package/node_modules/@forge/flow/src/monitor-runtime.js +460 -0
- package/node_modules/@forge/flow/src/process-lifecycle.js +469 -0
- package/node_modules/@forge/flow/src/skill-runtime.js +343 -0
- package/node_modules/@forge/memory/index.js +331 -0
- package/node_modules/@forge/memory/package.json +34 -0
- package/node_modules/@forge/memory/src/authority-provider.js +67 -0
- package/node_modules/@forge/memory/src/backend-registry.js +166 -0
- package/node_modules/@forge/memory/src/feedback-intake.js +243 -0
- package/node_modules/@forge/memory/src/pr-lifecycle-authority.js +946 -0
- package/node_modules/@forge/memory/src/usage-evidence.js +205 -0
- package/package.json +16 -4
- package/packages/flow/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/index.js +32 -0
- package/packages/flow/node_modules/@forge/contracts/package.json +35 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/flow/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/flow/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/flow/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/flow/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/flow/node_modules/@forge/contracts/src/validate.js +305 -0
- package/packages/memory/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/index.js +32 -0
- package/packages/memory/node_modules/@forge/contracts/package.json +35 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/memory/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/memory/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/memory/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/memory/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/memory/node_modules/@forge/contracts/src/validate.js +305 -0
- package/scripts/commitlint.js +13 -15
- package/scripts/generate-risk-manifest.js +91 -0
- package/scripts/github-context-bridge.sh +10 -0
- package/scripts/legacy-claim-repair.js +145 -0
- package/scripts/lib/behavioral-eval-runtime.js +3 -2
- package/scripts/process-tree.js +14 -2
- package/scripts/protected-state-check.js +440 -17
- package/scripts/sync-agent-skills.js +333 -34
- package/scripts/test-full-suite.js +704 -18
- package/scripts/test-profile.js +13 -3
- package/scripts/test.js +96 -15
- package/skills/coverage.json +1 -0
- package/skills/review/SKILL.md +2 -0
- package/skills/review/evals/scorecard.json +2 -2
- package/skills/setup/SKILL.md +18 -0
- package/skills/setup/evals/scorecard.json +3 -3
- package/skills/shepherd/SKILL.md +19 -2
- package/skills/shepherd/evals/scorecard.json +3 -3
- package/skills/validate/SKILL.md +3 -0
- package/skills/validate/evals/scorecard.json +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isLeaseExpired, isValidExpiresAt } = require('./lease-enforcer');
|
|
4
|
+
const { isTerminalStatus } = require('./taxonomy-validator');
|
|
5
|
+
|
|
6
|
+
// One authority definition for every user-facing lease projection. Null expiry is
|
|
7
|
+
// the compatibility form for a durable lease; malformed expiry cannot prove liveness.
|
|
8
|
+
function isLiveClaim(claim, issue, now) {
|
|
9
|
+
if (!claim || !issue) return false;
|
|
10
|
+
if (claim.state !== 'active') return false;
|
|
11
|
+
if (isTerminalStatus(issue.status)) return false;
|
|
12
|
+
if (!isValidExpiresAt(claim.expires_at)) return false;
|
|
13
|
+
return !isLeaseExpired(claim, now);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function projectLiveClaims(claims, issues, now) {
|
|
17
|
+
const claimRows = claims ?? [];
|
|
18
|
+
const issueRows = issues ?? [];
|
|
19
|
+
const issuesById = new Map(issueRows.map(issue => [issue.id, issue]));
|
|
20
|
+
return claimRows.filter(claim => isLiveClaim(claim, issuesById.get(claim.issue_id), now));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
isLiveClaim,
|
|
25
|
+
projectLiveClaims,
|
|
26
|
+
};
|
package/lib/kernel/migrations.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { getKernelSchema, validateKernelSchema } = require('./schema');
|
|
2
|
+
const { USAGE_EVIDENCE_MIGRATION } = require('@forge/memory');
|
|
2
3
|
|
|
3
4
|
function assertIdentifier(identifier, label) {
|
|
4
5
|
if (!/^[a-z][a-z0-9_]*$/.test(identifier)) {
|
|
@@ -24,12 +25,21 @@ function renderColumn(field) {
|
|
|
24
25
|
if (field.primaryKey) parts.push('PRIMARY KEY');
|
|
25
26
|
if (field.default !== undefined) parts.push(`DEFAULT ${field.default}`);
|
|
26
27
|
if (field.references) parts.push(renderReference(field.references));
|
|
28
|
+
if (field.check) parts.push(`CHECK (${field.check})`);
|
|
27
29
|
return parts.join(' ');
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
function renderCreateTable(table) {
|
|
31
33
|
assertIdentifier(table.sqlName, 'table');
|
|
32
|
-
const
|
|
34
|
+
const definitions = table.fields.map(field => ` ${renderColumn(field)}`);
|
|
35
|
+
if (table.primaryKey) {
|
|
36
|
+
const columns = table.primaryKey.map(column => {
|
|
37
|
+
assertIdentifier(column, 'primary key column');
|
|
38
|
+
return column;
|
|
39
|
+
}).join(', ');
|
|
40
|
+
definitions.push(` PRIMARY KEY (${columns})`);
|
|
41
|
+
}
|
|
42
|
+
const columns = definitions.join(',\n');
|
|
33
43
|
return `CREATE TABLE IF NOT EXISTS ${table.sqlName} (\n${columns}\n);`;
|
|
34
44
|
}
|
|
35
45
|
|
|
@@ -57,8 +67,13 @@ function renderDropTable(table) {
|
|
|
57
67
|
// stays the full current schema; the named tables are filtered out of 001 so they are
|
|
58
68
|
// created exactly once by their dedicated migration (both on a fresh DB and, via the
|
|
59
69
|
// ledger, on an existing DB). KEEP IN SYNC with every new table-creating migration.
|
|
60
|
-
// memories → 005 ; pr → 009
|
|
61
|
-
const MIGRATION_ADDED_TABLES = [
|
|
70
|
+
// memories → 005 ; pr → 009 ; PR watch ownership → 012
|
|
71
|
+
const MIGRATION_ADDED_TABLES = [
|
|
72
|
+
'memories',
|
|
73
|
+
'pr',
|
|
74
|
+
'pr_watch_owners',
|
|
75
|
+
'pr_watch_migration_gate',
|
|
76
|
+
];
|
|
62
77
|
|
|
63
78
|
function getInitialKernelSchema() {
|
|
64
79
|
const schema = getKernelSchema();
|
|
@@ -304,6 +319,100 @@ function buildPrLinkageMigration() {
|
|
|
304
319
|
};
|
|
305
320
|
}
|
|
306
321
|
|
|
322
|
+
// 010: Memory-owned durable monitor evidence. This is intentionally additive and
|
|
323
|
+
// isolated from Kernel authority tables. Rollback is non-destructive: it turns off
|
|
324
|
+
// new monitor writes while retaining every event, outbox row, receipt, and cursor.
|
|
325
|
+
function buildMonitorDurabilityMigration() {
|
|
326
|
+
return {
|
|
327
|
+
id: '010_memory_monitor_durability',
|
|
328
|
+
apply: [
|
|
329
|
+
`CREATE TABLE IF NOT EXISTS memory_monitor_writer_state (
|
|
330
|
+
singleton INTEGER NOT NULL PRIMARY KEY CHECK (singleton = 1),
|
|
331
|
+
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1))
|
|
332
|
+
);`,
|
|
333
|
+
'INSERT OR IGNORE INTO memory_monitor_writer_state (singleton, enabled) VALUES (1, 1);',
|
|
334
|
+
`CREATE TABLE IF NOT EXISTS memory_monitor_events (
|
|
335
|
+
event_id TEXT NOT NULL PRIMARY KEY,
|
|
336
|
+
monitor_id TEXT NOT NULL,
|
|
337
|
+
sequence INTEGER NOT NULL,
|
|
338
|
+
content_hash TEXT NOT NULL,
|
|
339
|
+
envelope_json TEXT NOT NULL,
|
|
340
|
+
artifact_digest TEXT,
|
|
341
|
+
created_at TEXT NOT NULL,
|
|
342
|
+
UNIQUE (monitor_id, sequence)
|
|
343
|
+
);`,
|
|
344
|
+
`CREATE TABLE IF NOT EXISTS memory_monitor_outbox (
|
|
345
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
346
|
+
event_id TEXT NOT NULL REFERENCES memory_monitor_events(event_id),
|
|
347
|
+
target TEXT NOT NULL,
|
|
348
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
349
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
350
|
+
next_attempt_at TEXT,
|
|
351
|
+
created_at TEXT NOT NULL,
|
|
352
|
+
UNIQUE (event_id, target)
|
|
353
|
+
);`,
|
|
354
|
+
`CREATE TABLE IF NOT EXISTS memory_monitor_delivery_receipts (
|
|
355
|
+
event_id TEXT NOT NULL REFERENCES memory_monitor_events(event_id),
|
|
356
|
+
target TEXT NOT NULL,
|
|
357
|
+
attempt INTEGER NOT NULL,
|
|
358
|
+
content_hash TEXT NOT NULL,
|
|
359
|
+
envelope_json TEXT NOT NULL,
|
|
360
|
+
acknowledged INTEGER NOT NULL,
|
|
361
|
+
delivered_at TEXT NOT NULL,
|
|
362
|
+
outcome TEXT NOT NULL,
|
|
363
|
+
PRIMARY KEY (event_id, target, attempt)
|
|
364
|
+
);`,
|
|
365
|
+
`CREATE TABLE IF NOT EXISTS memory_monitor_cursors (
|
|
366
|
+
monitor_id TEXT NOT NULL,
|
|
367
|
+
target TEXT NOT NULL,
|
|
368
|
+
sequence INTEGER NOT NULL,
|
|
369
|
+
updated_at TEXT NOT NULL,
|
|
370
|
+
PRIMARY KEY (monitor_id, target)
|
|
371
|
+
);`,
|
|
372
|
+
`CREATE TABLE IF NOT EXISTS memory_monitor_receipts (
|
|
373
|
+
monitor_id TEXT NOT NULL PRIMARY KEY,
|
|
374
|
+
content_hash TEXT NOT NULL,
|
|
375
|
+
envelope_json TEXT NOT NULL,
|
|
376
|
+
owner_run_id TEXT NOT NULL,
|
|
377
|
+
terminal_state TEXT NOT NULL,
|
|
378
|
+
last_sequence INTEGER NOT NULL,
|
|
379
|
+
evidence_digest TEXT NOT NULL,
|
|
380
|
+
undelivered_cursor INTEGER,
|
|
381
|
+
created_at TEXT NOT NULL
|
|
382
|
+
);`,
|
|
383
|
+
'CREATE INDEX IF NOT EXISTS idx_memory_monitor_events_monitor_sequence ON memory_monitor_events (monitor_id, sequence);',
|
|
384
|
+
'CREATE INDEX IF NOT EXISTS idx_memory_monitor_outbox_pending ON memory_monitor_outbox (target, status, next_attempt_at, created_at);',
|
|
385
|
+
'CREATE INDEX IF NOT EXISTS idx_memory_monitor_delivery_receipts_event_target_attempt ON memory_monitor_delivery_receipts (event_id, target, attempt);',
|
|
386
|
+
],
|
|
387
|
+
rollback: [
|
|
388
|
+
'UPDATE memory_monitor_writer_state SET enabled = 0 WHERE singleton = 1;',
|
|
389
|
+
],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// 011: usage evidence is append-only and rollback-retained. The public Memory
|
|
394
|
+
// contract owns the DDL; Kernel only places it in its ledgered migration sequence.
|
|
395
|
+
function buildUsageEvidenceMigration() {
|
|
396
|
+
return {
|
|
397
|
+
id: USAGE_EVIDENCE_MIGRATION.id,
|
|
398
|
+
apply: [...USAGE_EVIDENCE_MIGRATION.apply],
|
|
399
|
+
rollback: [...USAGE_EVIDENCE_MIGRATION.rollback],
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function buildPrWatchOwnershipMigration() {
|
|
404
|
+
const ownerTableNames = new Set(['pr_watch_owners', 'pr_watch_migration_gate']);
|
|
405
|
+
const tables = getKernelSchema().tables.filter(table => ownerTableNames.has(table.name));
|
|
406
|
+
const migration = buildSchemaMigration({ version: 1, tables });
|
|
407
|
+
return {
|
|
408
|
+
id: '012_kernel_pr_watch_ownership',
|
|
409
|
+
apply: migration.apply,
|
|
410
|
+
rollback: [
|
|
411
|
+
"INSERT INTO kernel_pr_watch_migration_gate (singleton, state, snapshot_hash, conflict_code, updated_at) VALUES (1, 'quarantined', NULL, NULL, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ON CONFLICT(singleton) DO UPDATE SET state = 'quarantined', conflict_code = NULL, updated_at = excluded.updated_at;",
|
|
412
|
+
],
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
307
416
|
function validateKernelMigrations(migrations) {
|
|
308
417
|
const ids = new Set();
|
|
309
418
|
for (const migration of migrations) {
|
|
@@ -335,6 +444,9 @@ function buildKernelMigrationPlan(migrations = [
|
|
|
335
444
|
buildWorktreeLinkageColumnsMigration(),
|
|
336
445
|
buildMemoryFtsMigration(),
|
|
337
446
|
buildPrLinkageMigration(),
|
|
447
|
+
buildMonitorDurabilityMigration(),
|
|
448
|
+
buildUsageEvidenceMigration(),
|
|
449
|
+
buildPrWatchOwnershipMigration(),
|
|
338
450
|
]) {
|
|
339
451
|
validateKernelMigrations(migrations);
|
|
340
452
|
|
|
@@ -353,6 +465,9 @@ module.exports = {
|
|
|
353
465
|
buildKernelMigrationPlan,
|
|
354
466
|
buildMemoryFtsMigration,
|
|
355
467
|
buildMemoryProjectionMigration,
|
|
468
|
+
buildMonitorDurabilityMigration,
|
|
469
|
+
buildPrWatchOwnershipMigration,
|
|
470
|
+
buildUsageEvidenceMigration,
|
|
356
471
|
buildPrLinkageMigration,
|
|
357
472
|
buildSchemaMigration,
|
|
358
473
|
buildWorktreeLinkageColumnsMigration,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { getTypeBehavior, isTerminalStatus, normalizeRank, toEpochMillis } = require('./taxonomy-validator');
|
|
4
|
+
const { isLiveClaim } = require('./live-claim-projection');
|
|
4
5
|
|
|
5
6
|
// Readiness is a DERIVED read model (D18). `ready`/`blocked` are computed on demand
|
|
6
7
|
// from dependencies, claims, quarantine/conflicts, gates, defer windows, and policy —
|
|
@@ -18,6 +19,11 @@ const READINESS_REASONS = Object.freeze({
|
|
|
18
19
|
CLAIM: 'claimed',
|
|
19
20
|
DEFERRED: 'deferred',
|
|
20
21
|
POLICY: 'policy_disabled',
|
|
22
|
+
CONTRACT_MISSING: 'contract_missing',
|
|
23
|
+
CONTRACT_INVALID: 'contract_invalid',
|
|
24
|
+
CONTRACT_MISMATCH: 'contract_mismatch',
|
|
25
|
+
CONTRACT_ADOPTION_REQUIRED: 'contract_adoption_required',
|
|
26
|
+
CONTRACT_ADOPTION_UNVERIFIED: 'contract_adoption_unverified',
|
|
21
27
|
});
|
|
22
28
|
|
|
23
29
|
const READINESS_STATES = Object.freeze([
|
|
@@ -65,17 +71,11 @@ function isDeferred(issue, now) {
|
|
|
65
71
|
return deferMillis > nowMillis;
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
function findConflictingClaim(
|
|
69
|
-
const nowMillis = toEpochMillis(now);
|
|
74
|
+
function findConflictingClaim(issue, claims, actor, now) {
|
|
70
75
|
for (const claim of claims || []) {
|
|
71
|
-
if (claim.issue_id !==
|
|
72
|
-
if ((claim
|
|
76
|
+
if (claim.issue_id !== issue.id) continue;
|
|
77
|
+
if (!isLiveClaim(claim, issue, now)) continue;
|
|
73
78
|
if (actor && claim.actor === actor) continue; // the requesting actor's own claim does not block them
|
|
74
|
-
// An active row whose lease has elapsed is dead (validateClaim agrees) — it must not
|
|
75
|
-
// keep the issue out of the ready queue for other actors. If `now` is unknown we cannot
|
|
76
|
-
// confirm expiry, so the claim still blocks (conservative).
|
|
77
|
-
const expiresMillis = toEpochMillis(claim.expires_at);
|
|
78
|
-
if (expiresMillis !== null && nowMillis !== null && expiresMillis <= nowMillis) continue;
|
|
79
79
|
return claim;
|
|
80
80
|
}
|
|
81
81
|
return null;
|
|
@@ -129,6 +129,149 @@ function collectGateBlockers(context, reasons) {
|
|
|
129
129
|
return hasGate;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
function nonEmptyText(value) {
|
|
133
|
+
if (typeof value === 'string') return value.trim() !== '';
|
|
134
|
+
return Array.isArray(value)
|
|
135
|
+
&& value.length > 0
|
|
136
|
+
&& value.every(item => typeof item === 'string' && item.trim() !== '');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function isPlainJsonObject(value) {
|
|
140
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
141
|
+
const prototype = Object.getPrototypeOf(value);
|
|
142
|
+
return prototype === Object.prototype || prototype === null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function ownDataProperty(object, key) {
|
|
146
|
+
if (!isPlainJsonObject(object)) return undefined;
|
|
147
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
148
|
+
return descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')
|
|
149
|
+
? descriptor.value
|
|
150
|
+
: undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseMetadata(issue) {
|
|
154
|
+
const metadata = ownDataProperty(issue, 'metadata');
|
|
155
|
+
if (metadata == null) return {};
|
|
156
|
+
if (isPlainJsonObject(metadata)) {
|
|
157
|
+
return metadata;
|
|
158
|
+
}
|
|
159
|
+
if (typeof metadata !== 'string' || metadata.trim() === '') return {};
|
|
160
|
+
try {
|
|
161
|
+
const parsed = JSON.parse(metadata);
|
|
162
|
+
return isPlainJsonObject(parsed) ? parsed : {};
|
|
163
|
+
} catch {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getIssueContract(issue) {
|
|
169
|
+
const metadata = parseMetadata(issue);
|
|
170
|
+
const candidate = ownDataProperty(metadata, 'forge.contract');
|
|
171
|
+
const contract = isPlainJsonObject(candidate) ? candidate : {};
|
|
172
|
+
return {
|
|
173
|
+
contract: contract && typeof contract === 'object' && !Array.isArray(contract) ? contract : {},
|
|
174
|
+
metadata,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function normalizeIds(ids) {
|
|
179
|
+
return [...new Set((Array.isArray(ids) ? ids : []).map(String))]
|
|
180
|
+
.sort((left, right) => left.localeCompare(right));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function collectContractFieldReasons(issue, contract, context) {
|
|
184
|
+
const reasons = [];
|
|
185
|
+
if (!nonEmptyText(issue.body)) reasons.push({ code: READINESS_REASONS.CONTRACT_MISSING, field: 'purpose' });
|
|
186
|
+
if (!nonEmptyText(issue.acceptance_criteria)) {
|
|
187
|
+
reasons.push({ code: READINESS_REASONS.CONTRACT_MISSING, field: 'acceptance_criteria' });
|
|
188
|
+
}
|
|
189
|
+
const risk = ownDataProperty(contract, 'risk');
|
|
190
|
+
const dependencies = ownDataProperty(contract, 'dependencies');
|
|
191
|
+
const outOfScope = ownDataProperty(contract, 'out_of_scope');
|
|
192
|
+
const version = ownDataProperty(contract, 'version');
|
|
193
|
+
if (!nonEmptyText(risk)) reasons.push({ code: READINESS_REASONS.CONTRACT_MISSING, field: 'risk' });
|
|
194
|
+
if (!Array.isArray(dependencies)) {
|
|
195
|
+
reasons.push({ code: READINESS_REASONS.CONTRACT_MISSING, field: 'dependencies' });
|
|
196
|
+
}
|
|
197
|
+
if (!nonEmptyText(outOfScope)) {
|
|
198
|
+
reasons.push({ code: READINESS_REASONS.CONTRACT_MISSING, field: 'out_of_scope' });
|
|
199
|
+
}
|
|
200
|
+
if (version !== 1) {
|
|
201
|
+
reasons.push({
|
|
202
|
+
code: READINESS_REASONS.CONTRACT_INVALID,
|
|
203
|
+
field: 'version',
|
|
204
|
+
expected: 1,
|
|
205
|
+
actual: version ?? null,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (Array.isArray(dependencies) && Array.isArray(context.dependencyIds)) {
|
|
209
|
+
const declared = normalizeIds(dependencies);
|
|
210
|
+
const expected = normalizeIds(context.dependencyIds);
|
|
211
|
+
if (JSON.stringify(declared) !== JSON.stringify(expected)) {
|
|
212
|
+
reasons.push({
|
|
213
|
+
code: READINESS_REASONS.CONTRACT_MISMATCH,
|
|
214
|
+
field: 'dependencies',
|
|
215
|
+
expected,
|
|
216
|
+
declared,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return reasons;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function isTrustedContractAdoption(issue, metadata, adoptedBy, version, context) {
|
|
224
|
+
const revision = ownDataProperty(issue, 'revision') ?? ownDataProperty(issue, 'entity_revision');
|
|
225
|
+
const issueId = ownDataProperty(issue, 'id');
|
|
226
|
+
if (typeof context.isTrustedAdoption !== 'function'
|
|
227
|
+
|| !nonEmptyText(issueId)
|
|
228
|
+
|| !Number.isInteger(revision)) return false;
|
|
229
|
+
try {
|
|
230
|
+
return context.isTrustedAdoption({
|
|
231
|
+
actor: adoptedBy,
|
|
232
|
+
issue_id: issueId,
|
|
233
|
+
revision,
|
|
234
|
+
metadata,
|
|
235
|
+
contract_version: version,
|
|
236
|
+
}) === true;
|
|
237
|
+
} catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function collectAdoptionReasons(issue, metadata, contract, context) {
|
|
243
|
+
const origin = ownDataProperty(issue, 'origin')
|
|
244
|
+
?? ownDataProperty(metadata, 'origin')
|
|
245
|
+
?? ownDataProperty(metadata, 'source');
|
|
246
|
+
if (!['import', 'imported', 'fork', 'fork_pr'].includes(origin)) return [];
|
|
247
|
+
const adoptedBy = ownDataProperty(contract, 'adopted_by');
|
|
248
|
+
if (!nonEmptyText(adoptedBy)) {
|
|
249
|
+
return [{ code: READINESS_REASONS.CONTRACT_ADOPTION_REQUIRED }];
|
|
250
|
+
}
|
|
251
|
+
const version = ownDataProperty(contract, 'version');
|
|
252
|
+
return isTrustedContractAdoption(issue, metadata, adoptedBy, version, context)
|
|
253
|
+
? []
|
|
254
|
+
: [{ code: READINESS_REASONS.CONTRACT_ADOPTION_UNVERIFIED, adopter: adoptedBy }];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Validate only the presence, shape, and authority agreement of an issue contract.
|
|
259
|
+
* The policy deliberately does not score prose, dictate reasoning, or require headings.
|
|
260
|
+
*/
|
|
261
|
+
function evaluateIssueContract(issue = {}, policy = {}, context = {}) {
|
|
262
|
+
const workClasses = Array.isArray(policy.workClasses) ? policy.workClasses : [];
|
|
263
|
+
const applicable = policy.enabled === true && workClasses.includes(issue.type);
|
|
264
|
+
if (!applicable) return { applicable: false, valid: true, reasons: [] };
|
|
265
|
+
|
|
266
|
+
const { contract, metadata } = getIssueContract(issue);
|
|
267
|
+
const reasons = [
|
|
268
|
+
...collectContractFieldReasons(issue, contract, context),
|
|
269
|
+
...collectAdoptionReasons(issue, metadata, contract, context),
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
return { applicable: true, valid: reasons.length === 0, reasons };
|
|
273
|
+
}
|
|
274
|
+
|
|
132
275
|
// Precedence high→low. `blocked` (dependencies/quarantine/conflict) always outranks the
|
|
133
276
|
// softer not-ready reasons; consumers picking next work should read the full reasons[]
|
|
134
277
|
// because a claim hidden behind a defer window is not reflected in this single summary.
|
|
@@ -156,15 +299,30 @@ function deriveReadiness(issue = {}, context = {}) {
|
|
|
156
299
|
return { id, status, ready: false, blocked: false, blocked_by: [], reasons: [], state: 'closed' };
|
|
157
300
|
}
|
|
158
301
|
|
|
302
|
+
const contract = evaluateIssueContract(issue, context.contractPolicy, {
|
|
303
|
+
dependencyIds: context.dependencyIds,
|
|
304
|
+
isTrustedAdoption: context.isTrustedAdoption,
|
|
305
|
+
});
|
|
306
|
+
|
|
159
307
|
// A `backlog` (parked) issue is a first-class lifecycle state, not workable work: it
|
|
160
308
|
// reads as parked regardless of any dependencies/gates — never ready, never blocked —
|
|
161
309
|
// so parked ideas do not surface in ready/blocked queues or flag stale.
|
|
162
310
|
if (status === 'backlog') {
|
|
163
|
-
return {
|
|
311
|
+
return {
|
|
312
|
+
id,
|
|
313
|
+
status,
|
|
314
|
+
ready: false,
|
|
315
|
+
blocked: false,
|
|
316
|
+
blocked_by: [],
|
|
317
|
+
reasons: contract.reasons,
|
|
318
|
+
state: 'backlog',
|
|
319
|
+
contract_applicable: contract.applicable,
|
|
320
|
+
};
|
|
164
321
|
}
|
|
165
322
|
|
|
166
323
|
const reasons = [];
|
|
167
324
|
const blockedBy = [];
|
|
325
|
+
reasons.push(...contract.reasons);
|
|
168
326
|
|
|
169
327
|
collectDependencyBlockers(context, reasons, blockedBy);
|
|
170
328
|
const hasConflict = collectConflictBlockers(issue, context, reasons);
|
|
@@ -180,7 +338,7 @@ function deriveReadiness(issue = {}, context = {}) {
|
|
|
180
338
|
reasons.push({ code: READINESS_REASONS.POLICY });
|
|
181
339
|
}
|
|
182
340
|
|
|
183
|
-
const conflictingClaim = findConflictingClaim(
|
|
341
|
+
const conflictingClaim = findConflictingClaim(issue, context.claims, context.actor, context.now);
|
|
184
342
|
if (conflictingClaim) {
|
|
185
343
|
reasons.push({ code: READINESS_REASONS.CLAIM, actor: conflictingClaim.actor });
|
|
186
344
|
}
|
|
@@ -191,6 +349,7 @@ function deriveReadiness(issue = {}, context = {}) {
|
|
|
191
349
|
&& !deferred
|
|
192
350
|
&& !policyDisabled
|
|
193
351
|
&& !conflictingClaim
|
|
352
|
+
&& contract.valid
|
|
194
353
|
&& isWorkableStatus(status)
|
|
195
354
|
&& isReadyEligibleType(issue.type);
|
|
196
355
|
|
|
@@ -203,7 +362,16 @@ function deriveReadiness(issue = {}, context = {}) {
|
|
|
203
362
|
ready,
|
|
204
363
|
});
|
|
205
364
|
|
|
206
|
-
return {
|
|
365
|
+
return {
|
|
366
|
+
id,
|
|
367
|
+
status,
|
|
368
|
+
ready,
|
|
369
|
+
blocked,
|
|
370
|
+
blocked_by: blockedBy,
|
|
371
|
+
reasons,
|
|
372
|
+
state,
|
|
373
|
+
contract_applicable: contract.applicable,
|
|
374
|
+
};
|
|
207
375
|
}
|
|
208
376
|
|
|
209
377
|
// BETA stop-gap for claim thundering-herd (kernel 369c43d7): concurrent `forge ready`
|
|
@@ -302,6 +470,9 @@ function buildReadinessIndex(input = {}) {
|
|
|
302
470
|
claims: claimsByIssue.get(issue.id) || [],
|
|
303
471
|
gates: gatesByIssue.get(issue.id) || [],
|
|
304
472
|
policyDisabled: policyDisabledIds.has(issue.id),
|
|
473
|
+
contractPolicy: input.contractPolicy,
|
|
474
|
+
dependencyIds: (dependencyStatusesByIssue.get(issue.id) || []).map(dependency => dependency.id),
|
|
475
|
+
isTrustedAdoption: input.isTrustedAdoption,
|
|
305
476
|
});
|
|
306
477
|
}
|
|
307
478
|
|
|
@@ -325,5 +496,6 @@ module.exports = {
|
|
|
325
496
|
DEFAULT_TOP_K_READY_PICK,
|
|
326
497
|
buildReadinessIndex,
|
|
327
498
|
deriveReadiness,
|
|
499
|
+
evaluateIssueContract,
|
|
328
500
|
isWorkableStatus,
|
|
329
501
|
};
|
package/lib/kernel/schema.js
CHANGED
|
@@ -34,6 +34,7 @@ function field(name, type, options = {}) {
|
|
|
34
34
|
notNull: Boolean(options.notNull || options.primaryKey),
|
|
35
35
|
default: options.default,
|
|
36
36
|
references: options.references,
|
|
37
|
+
check: options.check,
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -45,7 +46,7 @@ function index(name, columns, options = {}) {
|
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
function table(name, storageClass, fields, indexes = []) {
|
|
49
|
+
function table(name, storageClass, fields, indexes = [], options = {}) {
|
|
49
50
|
return {
|
|
50
51
|
name,
|
|
51
52
|
sqlName: `kernel_${name}`,
|
|
@@ -56,6 +57,7 @@ function table(name, storageClass, fields, indexes = []) {
|
|
|
56
57
|
storageClass: fieldDefinition.storageClass || storageClass,
|
|
57
58
|
})),
|
|
58
59
|
indexes,
|
|
60
|
+
...(options.primaryKey === undefined ? {} : { primaryKey: options.primaryKey }),
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -313,6 +315,28 @@ const TABLE_LIST = deepFreeze([
|
|
|
313
315
|
index('idx_pr_common_dir_state_repo_number', ['git_common_dir', 'state', 'repo', 'number']),
|
|
314
316
|
index('idx_pr_common_dir_repo_number', ['git_common_dir', 'repo', 'number'], { unique: true }),
|
|
315
317
|
]),
|
|
318
|
+
table('pr_watch_owners', 'authority', [
|
|
319
|
+
field('repo', 'TEXT', { notNull: true }),
|
|
320
|
+
field('pr', 'INTEGER', { notNull: true }),
|
|
321
|
+
field('version', 'INTEGER', { notNull: true, default: '1' }),
|
|
322
|
+
field('generation', 'TEXT', { notNull: true }),
|
|
323
|
+
field('phase', 'TEXT', { notNull: true }),
|
|
324
|
+
field('controller_pid', 'INTEGER'),
|
|
325
|
+
field('watcher_pid', 'INTEGER'),
|
|
326
|
+
field('started_at', 'TEXT', { notNull: true }),
|
|
327
|
+
field('updated_at', 'TEXT', { notNull: true }),
|
|
328
|
+
field('heartbeat_at', 'TEXT'),
|
|
329
|
+
field('terminal_receipt_id', 'TEXT'),
|
|
330
|
+
field('block_reason', 'TEXT'),
|
|
331
|
+
field('legacy_evidence_hash', 'TEXT'),
|
|
332
|
+
], [], { primaryKey: ['repo', 'pr'] }),
|
|
333
|
+
table('pr_watch_migration_gate', 'authority', [
|
|
334
|
+
field('singleton', 'INTEGER', { primaryKey: true, check: 'singleton = 1' }),
|
|
335
|
+
field('state', 'TEXT', { notNull: true }),
|
|
336
|
+
field('snapshot_hash', 'TEXT'),
|
|
337
|
+
field('conflict_code', 'TEXT'),
|
|
338
|
+
field('updated_at', 'TEXT', { notNull: true }),
|
|
339
|
+
]),
|
|
316
340
|
]);
|
|
317
341
|
|
|
318
342
|
const KERNEL_TABLES = deepFreeze(Object.fromEntries(TABLE_LIST.map(candidate => [candidate.name, candidate])));
|
|
@@ -344,6 +368,29 @@ function validateKernelSchema(schema = getKernelSchema()) {
|
|
|
344
368
|
if (!KERNEL_FIELD_AUTHORITIES.includes(tableDefinition.authority)) {
|
|
345
369
|
throw new Error(`Invalid table authority for ${tableDefinition.name}: ${tableDefinition.authority}`);
|
|
346
370
|
}
|
|
371
|
+
if (tableDefinition.primaryKey !== undefined) {
|
|
372
|
+
if (!Array.isArray(tableDefinition.primaryKey) || tableDefinition.primaryKey.length === 0) {
|
|
373
|
+
throw new Error(`Invalid table primary key for ${tableDefinition.name}: expected a non-empty column array`);
|
|
374
|
+
}
|
|
375
|
+
const primaryKeyColumns = new Set();
|
|
376
|
+
const fieldsByName = new Map(tableDefinition.fields.map(fieldDefinition => [fieldDefinition.name, fieldDefinition]));
|
|
377
|
+
if (tableDefinition.fields.some(fieldDefinition => fieldDefinition.primaryKey)) {
|
|
378
|
+
throw new Error(`Mixed field and table primary keys for ${tableDefinition.name}`);
|
|
379
|
+
}
|
|
380
|
+
for (const column of tableDefinition.primaryKey) {
|
|
381
|
+
if (primaryKeyColumns.has(column)) {
|
|
382
|
+
throw new Error(`Duplicate table primary key column for ${tableDefinition.name}: ${column}`);
|
|
383
|
+
}
|
|
384
|
+
primaryKeyColumns.add(column);
|
|
385
|
+
const primaryKeyField = fieldsByName.get(column);
|
|
386
|
+
if (!primaryKeyField) {
|
|
387
|
+
throw new Error(`Unknown table primary key column for ${tableDefinition.name}: ${column}`);
|
|
388
|
+
}
|
|
389
|
+
if (!primaryKeyField.notNull) {
|
|
390
|
+
throw new Error(`Nullable table primary key column for ${tableDefinition.name}: ${column}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
347
394
|
for (const fieldDefinition of tableDefinition.fields) {
|
|
348
395
|
if (!KERNEL_STORAGE_CLASSES.includes(fieldDefinition.storageClass)) {
|
|
349
396
|
throw new Error(`Invalid storage class for ${tableDefinition.name}.${fieldDefinition.name}: ${fieldDefinition.storageClass}`);
|
|
@@ -361,6 +408,7 @@ function validateKernelSchema(schema = getKernelSchema()) {
|
|
|
361
408
|
function cloneTableList(tableList) {
|
|
362
409
|
return tableList.map(tableDefinition => ({
|
|
363
410
|
...tableDefinition,
|
|
411
|
+
...(Array.isArray(tableDefinition.primaryKey) ? { primaryKey: [...tableDefinition.primaryKey] } : {}),
|
|
364
412
|
fields: tableDefinition.fields.map(fieldDefinition => ({ ...fieldDefinition })),
|
|
365
413
|
indexes: tableDefinition.indexes.map(indexDefinition => ({
|
|
366
414
|
...indexDefinition,
|