test: add packed consumer install smoke

This commit is contained in:
Eve
2026-05-08 15:47:18 +08:00
parent 54ad955ac2
commit 8a91206e07
8 changed files with 738 additions and 3 deletions

View File

@@ -269,5 +269,19 @@ npm run smoke
reporting-governance-package-smoke --compact reporting-governance-package-smoke --compact
``` ```
This smoke path uses package-local `profiles-src/`, `schemas/`, and `scripts/` only. Repeatable packed-consumer install smoke is now also covered by package test:
```bash
cd plugins/reporting-governance
npm pack
node --test test/packed-consumer-install.smoke.test.mjs
```
That path verifies a clean temp consumer can `npm install <tarball>` and then use only declared public surfaces:
- package root export: `@openclaw/plugin-reporting-governance`
- exported subpath: `@openclaw/plugin-reporting-governance/adapters/orchestrator`
- package bin: `reporting-governance-package-smoke`
This smoke path uses package-local `profiles-src/`, `schemas/`, and `scripts` only.
It writes temp runtime artifacts under a caller-provided or temp workspace and verifies the dry-run orchestrator path end to end. It writes temp runtime artifacts under a caller-provided or temp workspace and verifies the dry-run orchestrator path end to end.

View File

@@ -11,6 +11,9 @@
"ajv": "^8.17.1", "ajv": "^8.17.1",
"ajv-formats": "^3.0.1", "ajv-formats": "^3.0.1",
"yaml": "^2.8.0" "yaml": "^2.8.0"
},
"bin": {
"reporting-governance-package-smoke": "scripts/package-smoke.mjs"
} }
}, },
"node_modules/ajv": { "node_modules/ajv": {

View File

@@ -17,7 +17,7 @@
"reporting-governance-package-smoke": "./scripts/package-smoke.mjs" "reporting-governance-package-smoke": "./scripts/package-smoke.mjs"
}, },
"scripts": { "scripts": {
"test": "node --test test/package-structure.test.mjs test/policy-evaluator.test.mjs test/compatibility-preflight.test.mjs test/profile-artifact.test.mjs test/profile-generator.test.mjs test/decision-runner.test.mjs test/decision-store.test.mjs test/decision-store-runtime.integration.test.mjs test/governance-contract.integration.test.mjs test/watchdog-chain.integration.test.mjs test/runtime-integrated.integration.test.mjs test/exports-boundary.integration.test.mjs", "test": "node --test test/package-structure.test.mjs test/policy-evaluator.test.mjs test/compatibility-preflight.test.mjs test/profile-artifact.test.mjs test/profile-generator.test.mjs test/decision-runner.test.mjs test/decision-store.test.mjs test/decision-store-runtime.integration.test.mjs test/governance-contract.integration.test.mjs test/watchdog-chain.integration.test.mjs test/runtime-integrated.integration.test.mjs test/exports-boundary.integration.test.mjs test/packed-consumer-install.smoke.test.mjs",
"smoke": "node ./scripts/package-smoke.mjs --compact" "smoke": "node ./scripts/package-smoke.mjs --compact"
}, },
"dependencies": { "dependencies": {

View File

@@ -0,0 +1,260 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://cowbay.org/schemas/reporting-governance/decision-record-artifact.schema.json",
"title": "Reporting Governance Decision Record Artifact",
"description": "Portable decision storage artifact combining canonical decision output, truthful receipt state, and source linkage.",
"type": "object",
"additionalProperties": false,
"required": ["kind", "apiVersion", "metadata", "spec"],
"properties": {
"kind": { "const": "DecisionRecordArtifact" },
"apiVersion": { "const": "reporting-governance/v1alpha1" },
"metadata": { "$ref": "#/$defs/metadata" },
"spec": { "$ref": "#/$defs/spec" }
},
"$defs": {
"canonicalTimestamp": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$"
},
"sourceEventId": {
"type": "string",
"minLength": 1,
"pattern": "^(?:evt_[A-Za-z0-9._:-]+|/?[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$"
},
"metadata": {
"type": "object",
"additionalProperties": false,
"required": ["record_id", "recorded_at", "policy_id", "decision", "correlation_id", "task_id", "event_id"],
"properties": {
"record_id": { "type": "string", "minLength": 1 },
"recorded_at": { "$ref": "#/$defs/canonicalTimestamp" },
"policy_id": { "type": "string", "minLength": 1 },
"decision": { "type": "string", "minLength": 1 },
"correlation_id": { "type": ["string", "null"] },
"task_id": { "type": ["string", "null"] },
"event_id": {
"oneOf": [
{ "$ref": "#/$defs/sourceEventId" },
{ "type": "null" }
]
}
}
},
"receiptAction": {
"type": "object",
"additionalProperties": true,
"required": ["action", "mandatory"],
"properties": {
"action": { "type": "string", "minLength": 1 },
"mandatory": { "type": "boolean" }
}
},
"blockedAction": {
"type": "object",
"additionalProperties": true,
"required": ["action", "mandatory"],
"properties": {
"action": { "type": "string", "minLength": 1 },
"mandatory": { "type": "boolean" },
"reason": { "type": "string" }
}
},
"receipt": {
"type": "object",
"additionalProperties": true,
"required": [
"policy_id",
"decision",
"status",
"delivery_state",
"enforcement_intent",
"blocked_actions",
"notes"
],
"properties": {
"policy_id": { "type": "string", "minLength": 1 },
"decision": { "type": "string", "minLength": 1 },
"status": { "enum": ["planned", "acked", "blocked", "degraded", "failed"] },
"delivery_state": { "enum": ["prepared", "queued", "dispatched", "pending_external_send", "acked", "blocked", "failed", "partial"] },
"operator_notice_required": { "type": "boolean" },
"enforcement_intent": {
"type": "array",
"items": { "$ref": "#/$defs/receiptAction" }
},
"blocked_actions": {
"type": "array",
"items": { "$ref": "#/$defs/blockedAction" }
},
"notes": {
"type": "array",
"items": { "type": "string" }
},
"failure_reason": { "type": "string", "minLength": 1 }
},
"allOf": [
{
"if": {
"properties": { "status": { "const": "failed" } },
"required": ["status"]
},
"then": { "required": ["failure_reason"] }
},
{
"if": {
"properties": { "delivery_state": { "const": "partial" } },
"required": ["delivery_state"]
},
"then": {
"properties": {
"blocked_actions": { "minItems": 1 },
"enforcement_intent": { "minItems": 1 }
}
}
},
{
"if": {
"properties": { "status": { "const": "planned" } },
"required": ["status"]
},
"then": {
"properties": {
"delivery_state": { "enum": ["prepared", "queued", "dispatched", "pending_external_send", "partial"] }
}
}
},
{
"if": {
"properties": { "status": { "const": "acked" } },
"required": ["status"]
},
"then": {
"properties": {
"delivery_state": { "const": "acked" }
}
}
},
{
"if": {
"properties": { "status": { "const": "blocked" } },
"required": ["status"]
},
"then": {
"properties": {
"delivery_state": { "const": "blocked" }
}
}
},
{
"if": {
"properties": { "status": { "const": "degraded" } },
"required": ["status"]
},
"then": {
"properties": {
"delivery_state": { "enum": ["partial", "failed", "blocked"] }
}
}
},
{
"if": {
"properties": { "status": { "const": "failed" } },
"required": ["status"]
},
"then": {
"properties": {
"delivery_state": { "const": "failed" }
}
}
}
]
},
"source": {
"type": "object",
"additionalProperties": false,
"required": ["event_id", "task_id", "correlation_id"],
"properties": {
"event_id": {
"oneOf": [
{ "$ref": "#/$defs/sourceEventId" },
{ "type": "null" }
]
},
"task_id": { "type": ["string", "null"] },
"correlation_id": { "type": ["string", "null"] }
}
},
"spec": {
"type": "object",
"additionalProperties": false,
"required": ["decision", "receipt", "source"],
"properties": {
"decision": { "$ref": "decision.schema.json" },
"receipt": { "$ref": "#/$defs/receipt" },
"source": { "$ref": "#/$defs/source" }
}
}
},
"allOf": [
{
"if": {
"properties": {
"metadata": {
"properties": {
"event_id": { "type": "null" }
},
"required": ["event_id"]
}
}
},
"then": {
"properties": {
"spec": {
"properties": {
"source": {
"properties": {
"event_id": { "type": "null" }
},
"required": ["event_id"]
}
}
}
}
},
"else": {
"properties": {
"spec": {
"properties": {
"source": {
"properties": {
"event_id": { "$ref": "#/$defs/sourceEventId" }
},
"required": ["event_id"]
}
}
}
}
}
},
{
"if": {
"properties": {
"spec": {
"properties": {
"source": {
"properties": {
"task_id": { "type": "null" },
"correlation_id": { "type": "null" },
"event_id": { "type": "null" }
},
"required": ["task_id", "correlation_id", "event_id"]
}
},
"required": ["source"]
}
}
},
"then": false
}
]
}

View File

@@ -0,0 +1,376 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://cowbay.org/schemas/reporting-governance/decision.schema.json",
"title": "Reporting Governance Decision",
"description": "Canonical policy decision schema for the reporting-governance plugin.",
"type": "object",
"additionalProperties": false,
"required": [
"decision",
"policy_id",
"severity",
"reason",
"rewritten_message",
"suggested_status",
"required_actions",
"operator_notice"
],
"properties": {
"decision": {
"$ref": "#/$defs/decision",
"description": "Canonical enforcement disposition."
},
"policy_id": {
"type": "string",
"minLength": 1,
"description": "Stable policy rule identifier that produced the decision."
},
"severity": {
"$ref": "#/$defs/severity",
"description": "Risk or urgency classification for the decision."
},
"reason": {
"type": "string",
"minLength": 1,
"description": "Human-readable rationale for the decision."
},
"rewritten_message": {
"type": ["string", "null"],
"description": "Replacement or annotated outgoing message text when governance rewrites or forces placeholder disclosure."
},
"suggested_status": {
"$ref": "#/$defs/suggestedStatusOrNull",
"description": "Recommended workflow status after enforcement, if any."
},
"required_actions": {
"type": "array",
"items": {
"$ref": "#/$defs/requiredAction"
},
"description": "Ordered list of required enforcement actions."
},
"operator_notice": {
"$ref": "#/$defs/operatorNoticeOrNull",
"description": "Operator-visible notice requirement produced by the policy."
}
},
"$defs": {
"decision": {
"type": "string",
"enum": [
"allow",
"rewrite",
"block",
"require_review",
"force_checkpoint",
"escalate",
"downgrade_status",
"annotate_placeholder"
]
},
"severity": {
"type": "string",
"enum": [
"info",
"low",
"medium",
"high",
"critical"
]
},
"suggestedStatus": {
"type": "string",
"enum": [
"in_progress",
"pending_verification",
"blocked",
"failed",
"awaiting_review",
"completed"
]
},
"suggestedStatusOrNull": {
"oneOf": [
{
"$ref": "#/$defs/suggestedStatus"
},
{
"type": "null"
}
]
},
"actionVerb": {
"type": "string",
"enum": [
"dispatch_message",
"rewrite_message",
"append_audit_note",
"block_transition",
"set_status",
"request_review",
"emit_event",
"notify_operator",
"start_watchdog",
"raise_escalation",
"record_placeholder"
]
},
"actionTarget": {
"type": "string",
"enum": [
"outgoing_report",
"status_transition",
"operator_channel",
"task_record",
"event_stream",
"watchdog",
"review_queue"
]
},
"requiredAction": {
"type": "object",
"additionalProperties": false,
"required": [
"action",
"target",
"mandatory"
],
"properties": {
"action": {
"$ref": "#/$defs/actionVerb"
},
"target": {
"$ref": "#/$defs/actionTarget"
},
"mandatory": {
"type": "boolean"
},
"details": {
"type": "object",
"additionalProperties": true
}
}
},
"urgency": {
"type": "string",
"enum": [
"info",
"low",
"medium",
"high",
"critical"
]
},
"operatorNotice": {
"type": "object",
"additionalProperties": false,
"required": [
"required",
"channel",
"urgency",
"message",
"deadline"
],
"properties": {
"required": {
"type": "boolean"
},
"channel": {
"type": ["string", "null"]
},
"urgency": {
"oneOf": [
{
"$ref": "#/$defs/urgency"
},
{
"type": "null"
}
]
},
"message": {
"type": ["string", "null"]
},
"must_reference": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"deadline": {
"type": ["string", "null"],
"format": "date-time"
}
}
},
"operatorNoticeOrNull": {
"oneOf": [
{
"$ref": "#/$defs/operatorNotice"
},
{
"type": "null"
}
]
}
},
"allOf": [
{
"if": {
"properties": {
"decision": {
"const": "rewrite"
}
},
"required": ["decision"]
},
"then": {
"properties": {
"rewritten_message": {
"type": "string",
"minLength": 1
}
}
}
},
{
"if": {
"properties": {
"decision": {
"const": "annotate_placeholder"
}
},
"required": ["decision"]
},
"then": {
"properties": {
"rewritten_message": {
"type": "string",
"minLength": 1
},
"operator_notice": {
"$ref": "#/$defs/operatorNotice"
}
}
}
},
{
"if": {
"properties": {
"decision": {
"const": "force_checkpoint"
}
},
"required": ["decision"]
},
"then": {
"properties": {
"operator_notice": {
"$ref": "#/$defs/operatorNotice"
}
},
"allOf": [
{
"properties": {
"operator_notice": {
"properties": {
"required": {
"const": true
}
},
"required": ["required"]
}
}
},
{
"properties": {
"required_actions": {
"contains": {
"type": "object",
"properties": {
"action": {
"enum": ["notify_operator", "dispatch_message"]
},
"mandatory": {
"const": true
}
},
"required": ["action", "mandatory"]
}
}
}
}
]
}
},
{
"if": {
"properties": {
"decision": {
"const": "downgrade_status"
}
},
"required": ["decision"]
},
"then": {
"properties": {
"suggested_status": {
"const": "pending_verification"
},
"required_actions": {
"contains": {
"type": "object",
"properties": {
"action": {
"const": "set_status"
},
"mandatory": {
"const": true
},
"details": {
"type": "object",
"properties": {
"to": {
"const": "pending_verification"
}
},
"required": ["to"]
}
},
"required": ["action", "mandatory", "details"]
}
}
}
}
},
{
"if": {
"properties": {
"decision": {
"const": "block"
}
},
"required": ["decision"]
},
"then": {
"properties": {
"required_actions": {
"contains": {
"type": "object",
"properties": {
"action": {
"const": "block_transition"
},
"mandatory": {
"const": true
}
},
"required": ["action", "mandatory"]
}
}
}
}
}
]
}

View File

@@ -20,7 +20,7 @@ const RECEIPT_STATUS_RULES = {
}; };
const packageRoot = path.resolve(import.meta.dirname, '..', '..'); const packageRoot = path.resolve(import.meta.dirname, '..', '..');
const repoRoot = path.resolve(packageRoot, '..', '..'); const repoRoot = packageRoot;
const decisionSchemaPath = path.resolve(repoRoot, 'schemas', 'reporting-governance', 'decision.schema.json'); const decisionSchemaPath = path.resolve(repoRoot, 'schemas', 'reporting-governance', 'decision.schema.json');
const decisionRecordArtifactSchemaPath = path.resolve(repoRoot, 'schemas', 'reporting-governance', 'decision-record-artifact.schema.json'); const decisionRecordArtifactSchemaPath = path.resolve(repoRoot, 'schemas', 'reporting-governance', 'decision-record-artifact.schema.json');

View File

@@ -29,6 +29,8 @@ const requiredPaths = [
'profiles-src/strict-manager-mode.yaml', 'profiles-src/strict-manager-mode.yaml',
'schemas/reporting-governance/deployment-profile.schema.json', 'schemas/reporting-governance/deployment-profile.schema.json',
'schemas/reporting-governance/capability-descriptor.schema.json', 'schemas/reporting-governance/capability-descriptor.schema.json',
'schemas/reporting-governance/decision.schema.json',
'schemas/reporting-governance/decision-record-artifact.schema.json',
'scripts/package-smoke.mjs', 'scripts/package-smoke.mjs',
'scripts/watchdog_auto_notify_orchestrator.mjs', 'scripts/watchdog_auto_notify_orchestrator.mjs',
'scripts/long_task_watchdog.mjs', 'scripts/long_task_watchdog.mjs',

View File

@@ -0,0 +1,80 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
const packageRoot = path.resolve(import.meta.dirname, '..');
function run(command, args, { cwd, env = {} } = {}) {
const result = spawnSync(command, args, {
cwd,
encoding: 'utf8',
env: {
...process.env,
...env,
},
});
assert.equal(
result.status,
0,
[
`command failed: ${command} ${args.join(' ')}`,
`cwd: ${cwd}`,
result.stdout && `stdout:\n${result.stdout.trim()}`,
result.stderr && `stderr:\n${result.stderr.trim()}`,
].filter(Boolean).join('\n\n')
);
return result;
}
test('packed tarball installs into clean consumer and works via public exports/bin only', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'reporting-governance-packed-consumer-'));
try {
const packResult = run('npm', ['pack', '--json'], { cwd: packageRoot });
const packPayload = JSON.parse(packResult.stdout.trim());
const tarballName = packPayload.at(-1)?.filename;
assert.ok(tarballName, 'npm pack should return tarball filename');
const tarballPath = path.join(packageRoot, tarballName);
assert.equal(fs.existsSync(tarballPath), true, 'tarball should exist after npm pack');
const consumerRoot = path.join(root, 'consumer');
fs.mkdirSync(consumerRoot, { recursive: true });
run('npm', ['init', '-y'], { cwd: consumerRoot });
run('npm', ['install', tarballPath], { cwd: consumerRoot });
const exportProbe = run(process.execPath, ['--input-type=module', '--eval', `
import * as plugin from '@openclaw/plugin-reporting-governance';
import { runOrchestratorAdapter } from '@openclaw/plugin-reporting-governance/adapters/orchestrator';
process.stdout.write(JSON.stringify({
packageName: plugin.packageName,
hasRunWatchdogChain: typeof plugin.runWatchdogChain,
hasRunOrchestratorAdapter: typeof runOrchestratorAdapter,
}));
`], { cwd: consumerRoot });
const exportPayload = JSON.parse(exportProbe.stdout.trim());
assert.equal(exportPayload.packageName, '@openclaw/plugin-reporting-governance');
assert.equal(exportPayload.hasRunWatchdogChain, 'function');
assert.equal(exportPayload.hasRunOrchestratorAdapter, 'function');
const smokeResult = run(
path.join(consumerRoot, 'node_modules', '.bin', 'reporting-governance-package-smoke'),
['--compact'],
{ cwd: consumerRoot }
);
const smokePayload = JSON.parse(smokeResult.stdout.trim());
assert.equal(smokePayload.ok, true);
assert.equal(smokePayload.tool, 'reporting-governance-package-smoke');
assert.equal(smokePayload.orchestrator.ok, true);
assert.equal(smokePayload.orchestrator.dispatchedCount, 1);
assert.equal(smokePayload.orchestrator.pendingCount, 1);
assert.equal(smokePayload.orchestrator.notificationCount, 1);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});