@viccydev/pi-fpa 0.7.1 → 0.7.2

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/README.md CHANGED
@@ -148,12 +148,12 @@ pi list
148
148
  团队分发建议使用固定 Git tag:
149
149
 
150
150
  ```bash
151
- pi install git:github.com/linyqh/pi-fpa@v0.7.1
151
+ pi install git:github.com/linyqh/pi-fpa@v0.7.2
152
152
  ```
153
153
 
154
154
  ## 发布到 npm
155
155
 
156
- 发布动作由 GitHub Release 触发。Release 标签必须严格使用 `v<package.json version>`,例如版本 `0.7.1` 对应 `v0.7.1`。工作流会检出该标签,执行 `npm ci`、`npm test` 和包内容预检,全部通过后发布公开包 `@viccydev/pi-fpa`。普通 Release 发布到 `latest`,Prerelease 发布到 `next`。
156
+ 发布动作由 GitHub Release 触发。Release 标签必须严格使用 `v<package.json version>`,例如版本 `0.7.2` 对应 `v0.7.2`。工作流会检出该标签,执行 `npm ci`、`npm test` 和包内容预检,全部通过后发布公开包 `@viccydev/pi-fpa`。普通 Release 发布到 `latest`,Prerelease 发布到 `next`。
157
157
 
158
158
  发布认证使用 npm Trusted Publishing / OIDC,不使用长期 npm Token。npm 包后台的 Trusted Publisher 配置为:
159
159
 
@@ -58,11 +58,13 @@ export default function fpaDashboardExtension(pi: ExtensionAPI): void {
58
58
  label: "Refresh FP&A Dashboard",
59
59
  description:
60
60
  "Build the forecast closed-loop dashboard from a committed approved forecast, optional committed execution receipt, and current read-only FP&A Actuals. " +
61
- "Preview computes and validates without publishing; publish requires the exact preview fingerprint and atomically promotes a content-addressed generation.",
61
+ "Preview computes and validates without publishing; publish requires the exact preview fingerprint and atomically promotes a content-addressed generation. " +
62
+ "A backtest can be displayed only from an exact committed artifact when the user explicitly requests it.",
62
63
  promptSnippet: "Preview or atomically publish the forecast closed-loop FP&A dashboard",
63
64
  promptGuidelines: [
64
65
  "Always call fpa_dashboard_refresh with mode=preview before mode=publish and carry forward the exact preview_fingerprint.",
65
66
  "Never write .fpa-dashboard files with generic file or shell tools; use fpa_dashboard_refresh so calculations, lineage, and atomic publication stay consistent.",
67
+ "Set allow_backtest_display=true only when the user explicitly asks to display or publish an exact committed backtest; never infer this authorization from Graph completion.",
66
68
  ],
67
69
  parameters: Type.Object(
68
70
  {
@@ -70,6 +72,7 @@ export default function fpaDashboardExtension(pi: ExtensionAPI): void {
70
72
  mode: StringEnum(["preview", "publish"] as const),
71
73
  locale: Type.Optional(StringEnum(["zh-CN", "en-US"] as const)),
72
74
  expected_preview_fingerprint: Type.Optional(Type.String({ pattern: "^[a-f0-9]{64}$" })),
75
+ allow_backtest_display: Type.Optional(Type.Boolean()),
73
76
  scope_id: Type.Optional(Type.String({ minLength: 1, maxLength: 256 })),
74
77
  cycle_id: Type.Optional(Type.String({ minLength: 1, maxLength: 256 })),
75
78
  forecast_ref: Type.Optional(artifactRefSchema),
@@ -86,6 +89,9 @@ export default function fpaDashboardExtension(pi: ExtensionAPI): void {
86
89
  if (hasAnyExactField && exactFields.some((value) => value === undefined)) {
87
90
  throw new Error("scope_id, cycle_id, and forecast_ref must be supplied together; exact refresh never falls back to current artifacts.");
88
91
  }
92
+ if (params.allow_backtest_display && !hasAnyExactField) {
93
+ throw new Error("allow_backtest_display requires an exact scope_id, cycle_id, and forecast_ref.");
94
+ }
89
95
  const exact = hasAnyExactField ? {
90
96
  scopeId: params.scope_id as string,
91
97
  cycleId: params.cycle_id as string,
@@ -96,19 +102,24 @@ export default function fpaDashboardExtension(pi: ExtensionAPI): void {
96
102
  } : undefined;
97
103
  if (exact) {
98
104
  const exactForecast = await readArtifactByRef(ctx.cwd, exact.forecastRef);
99
- if (exactForecast.ledgerContext?.forecast_role === "backtest") {
100
- throw new Error("A backtest forecast cannot be previewed or published as the operating dashboard.");
105
+ const exactRole = exactForecast.ledgerContext?.forecast_role;
106
+ if (exactRole === "backtest" && params.allow_backtest_display !== true) {
107
+ throw new Error("Displaying a backtest requires explicit user authorization through allow_backtest_display=true.");
108
+ }
109
+ if (params.allow_backtest_display === true && exactRole !== "backtest") {
110
+ throw new Error("allow_backtest_display is valid only for an exact committed backtest.");
101
111
  }
102
112
  }
103
113
  const { build, forecast, actuals, sliceKeyMismatch, projector, runtime } = await buildDashboardProjection(ctx.cwd, params.preset, params.locale ?? "zh-CN", exact, signal);
104
- if (build.source.forecast_role === "backtest") {
105
- throw new Error("A backtest forecast cannot be previewed or published as the operating dashboard.");
114
+ if (build.source.forecast_role === "backtest" && params.allow_backtest_display !== true) {
115
+ throw new Error("Displaying a backtest requires explicit user authorization through allow_backtest_display=true.");
106
116
  }
107
117
  const previewFingerprint = dashboardBuildFingerprint(build, projector);
108
118
  const summary = {
109
119
  preset: params.preset,
110
120
  preview_fingerprint: previewFingerprint,
111
121
  forecast_version: forecast.forecast_version,
122
+ forecast_role: build.source.forecast_role,
112
123
  forecast_fingerprint: build.source.forecast_fingerprint,
113
124
  execution_fingerprint: build.source.execution_fingerprint,
114
125
  data_as_of: build.source.data_as_of,
@@ -146,7 +157,14 @@ export default function fpaDashboardExtension(pi: ExtensionAPI): void {
146
157
  if (params.expected_preview_fingerprint !== previewFingerprint) {
147
158
  throw new Error("Dashboard inputs changed after preview; run preview again before publishing.");
148
159
  }
149
- const published = await publishDashboard({ cwd: ctx.cwd, build, projector, runtime, actualsSnapshot: actuals });
160
+ const published = await publishDashboard({
161
+ cwd: ctx.cwd,
162
+ build,
163
+ projector,
164
+ runtime,
165
+ actualsSnapshot: actuals,
166
+ allowBacktestDisplay: params.allow_backtest_display === true,
167
+ });
150
168
  return toolResult({ status: "published", ...summary, dashboard_dir: published.dashboardDir, published_datasets: published.publishedDatasets });
151
169
  },
152
170
  });
@@ -22,6 +22,7 @@ export interface PublishDashboardOptions {
22
22
  runtime: DashboardProjectorRuntime;
23
23
  publishedAt?: string;
24
24
  actualsSnapshot?: DashboardActualsSnapshot;
25
+ allowBacktestDisplay?: boolean;
25
26
  }
26
27
 
27
28
  export interface PublishDashboardResult {
@@ -177,8 +178,8 @@ export function dashboardBuildFingerprint(build: DashboardBuild, projector: Dash
177
178
 
178
179
  export async function publishDashboard(options: PublishDashboardOptions): Promise<PublishDashboardResult> {
179
180
  assertBuild(options.build);
180
- if (options.build.source.forecast_role === "backtest") {
181
- throw new Error("A backtest forecast cannot replace the operating dashboard.");
181
+ if (options.build.source.forecast_role === "backtest" && options.allowBacktestDisplay !== true) {
182
+ throw new Error("A backtest cannot replace the operating dashboard without explicit user authorization.");
182
183
  }
183
184
  const projector = validateProjectorProvenance(options.projector);
184
185
  const runtime = validateProjectorRuntime(options.runtime);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viccydev/pi-fpa",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "description": "Full-cycle FP&A planning, strategy, forecast, and review prompts, skills, and data tools for Pi",
6
6
  "license": "UNLICENSED",
@@ -40,7 +40,9 @@ from it, return `blocked`; never reconstruct or substitute the handoff.
40
40
  period start and is the approved direct successor of the active cycle. Use
41
41
  `backtest` for a historical rerun frozen after its target
42
42
  period; it is immutable evidence, not an operating plan, and it must not
43
- replace the current pointer or enter the operating dashboard queue. A
43
+ replace the current pointer or enter the automatic operating dashboard
44
+ queue. It may be displayed from its exact immutable reference only when the
45
+ user explicitly requests a backtest dashboard publication. A
44
46
  `next_plan` is likewise stored as forward lineage and does not replace the
45
47
  legacy current pointer; its explicit dashboard handoff links it to the
46
48
  active cycle after successor validation.
@@ -26,14 +26,21 @@ only part of this tuple or fall back to a mutable current pointer.
26
26
  3. If the result is `enqueued` or `already_pending` and the current task expects
27
27
  the dashboard immediately, call one bounded `drain`, then verify with
28
28
  `fpa_dashboard_status`.
29
- 4. If the result is `not_applicable` for a `backtest`, report that the historical
30
- forecast is frozen and the operating dashboard intentionally remains
31
- unchanged. Never relabel the artifact to force it into the active lineage.
29
+ 4. If the result is `not_applicable` for a `backtest`, do not treat that as a
30
+ failed artifact. Automatic post-Graph handoff intentionally leaves the
31
+ operating dashboard unchanged. If the user explicitly asked to display or
32
+ publish that backtest, continue through the exact backtest display path
33
+ below; otherwise report that the frozen historical forecast remains in the
34
+ ledger without changing the dashboard.
32
35
 
33
36
  ### Human-requested rebuild
34
37
 
35
38
  1. Call `fpa_dashboard_status` to inspect the current generation and diagnostics.
36
- 2. Call `fpa_dashboard_refresh` with `preset: forecast-closed-loop-v1` and `mode: preview`.
39
+ 2. Call `fpa_dashboard_refresh` with `preset: forecast-closed-loop-v1` and
40
+ `mode: preview`. For a user-requested backtest display, pass the exact
41
+ `scope_id`, `cycle_id`, and `forecast_ref` from its immutable ledger handoff
42
+ together with `allow_backtest_display: true`. Never set that flag merely
43
+ because a Graph completed.
37
44
  3. Review the returned lineage, coverage, query receipts, warnings, widget list, and `preview_fingerprint`.
38
45
  4. If the user asked only to inspect or preview, stop without publishing.
39
46
  5. Publish only when the user explicitly asked to rebuild or publish the dashboard. Call the same tool with `mode: publish` and the exact preview fingerprint.
@@ -55,6 +62,10 @@ answer, and do not describe it as "the period has no data yet".
55
62
  - Never write `.fpa-dashboard` with generic file or shell tools.
56
63
  - Never fabricate Actuals, replace missing values with zero, or average row-level ratios.
57
64
  - Do not edit the approved forecast during projection.
65
+ - Never relabel a backtest as `original`, `eac`, or `next_plan` to display it.
66
+ - `allow_backtest_display` is explicit user authorization to make that exact
67
+ immutable backtest the displayed dashboard generation. Omit it for automatic
68
+ handoff and for every non-backtest forecast.
58
69
  - If inputs change between preview and publish, preview again rather than bypassing the fingerprint check.
59
70
  - Artifact commit never performs dashboard I/O. The calling main Agent explicitly
60
71
  enqueues its exact handoff. Actuals watermark changes remain monitored by the
@@ -8,5 +8,5 @@ The dashboard is a deterministic projection, not a second planning model.
8
8
  - Coverage: retain the Actuals cutoff and query receipts. Planned slices with no Actuals remain null; paid Actuals outside the approved allocation are reported as warnings.
9
9
  - Comparison: the prior-period window must contain the same number of covered calendar days as the current Actuals window, using the forecast timezone.
10
10
  - Execution truth: `reported` means reported, not verified. Only `verified` external evidence may be labelled verified.
11
- - Publication: preview first, then atomically publish content-addressed datasets and promote `manifest.json` last.
11
+ - Publication: preview first, then atomically publish content-addressed datasets and promote `manifest.json` last. Automatic handoff never promotes a backtest; an exact committed backtest may be displayed only after explicit user authorization through `allow_backtest_display` on both preview and publish.
12
12
  - Ownership: only `fpa_dashboard_refresh` may publish this projection.