@danypops/papyrus 0.44.6 → 0.44.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/papyrus",
3
- "version": "0.44.6",
3
+ "version": "0.44.8",
4
4
  "description": "Daemon-backed graph artifacts, evidence-bearing tasks, rules, skills, and native TUI workflows for Pi",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package"],
package/src/service.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { VehicleError } from "@danypops/vehicle-core";
1
2
  import type { VehicleRegistry } from "@danypops/vehicle-server";
2
3
  import { createVehicleHttpApp } from "@danypops/vehicle-server/http";
3
4
  import type { Logger } from "@danypops/vehicle-server/logging";
@@ -22,6 +23,7 @@ import type { CreateArtifactInput } from "./domain/artifact.ts";
22
23
  import type { TaskEventContext } from "./domain/task-event.ts";
23
24
  import type { TaskViewMode } from "./domain/task-scope.ts";
24
25
  import { listInjectableRules } from "./domain-services.ts";
26
+ import { logEvent } from "./log.ts";
25
27
  import { Logs } from "./log-service.ts";
26
28
  import { OperationRegistry } from "./module-registry.ts";
27
29
  import { DISCUSS_OPERATION_NAMES, discussOperations } from "./modules/discuss.ts";
@@ -588,7 +590,23 @@ export function createApp(deps: {
588
590
  return json({ error: "missing or invalid bearer token" }, { status: 401 });
589
591
  }
590
592
  const url = new URL(request.url);
591
- if (url.pathname.startsWith("/vehicle/")) return vehicleApp.fetch(request);
593
+ if (url.pathname.startsWith("/vehicle/")) {
594
+ // Every domain (tasks/docs/rules/discuss/notes/playbooks) is reachable here directly
595
+ // (registry.invoke()), bypassing execute()'s own migrationRequired guard below entirely --
596
+ // a stale schema surfaced as an opaque handler-failed (or worse, a silent wrong answer)
597
+ // the moment a request touched a column/table the old schema never had, instead of the
598
+ // same clear, actionable error execute() already gives. Real incident: restarting the
599
+ // live daemon onto new code without running `papyrus migrate schema` first.
600
+ if (deps.service.schemaState().migrationRequired) {
601
+ logEvent("warn", "vehicle_invoke_blocked_migration_required", { path: url.pathname, schema: deps.service.schemaState() });
602
+ const failure = new VehicleError("migration-required", "database migration required; run `papyrus migrate schema`", {
603
+ category: "unavailable",
604
+ retryable: false,
605
+ }).toFailure();
606
+ return json({ error: failure, operationId: crypto.randomUUID() }, { status: 503 });
607
+ }
608
+ return vehicleApp.fetch(request);
609
+ }
592
610
  if (request.method === "GET" && url.pathname === "/health") {
593
611
  return json({ ok: true, version: VERSION, schema: deps.service.schemaState() });
594
612
  }
@@ -42,6 +42,11 @@ export function createPapyrusVehicleRegistry(deps: PapyrusVehicleDeps): VehicleR
42
42
  version: "1.0.0",
43
43
  description: "Papyrus's graph-artifact domains, one honest operation per real action.",
44
44
  });
45
+ // An unexpected handler exception's message becomes the classified failure's causeMessage
46
+ // instead of vanishing into an opaque "<op>@N handler failed". Safe here: no Papyrus domain
47
+ // error ever embeds a session_secret or other credential in its message (only a bare
48
+ // session_id, a correlation id, not a secret -- see session-identity-service.ts).
49
+ registry.setExposeHandlerFailureDetails(true);
45
50
  registerNotesVehicleOperations(registry, deps.notes, deps.artifacts);
46
51
  registerRulesVehicleOperations(registry, deps.artifacts, deps.scopes);
47
52
  registerDocsVehicleOperations(registry, deps.artifacts, deps.scopes, deps.authority);