@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 +2 -2
- package/extensions/fpa-dashboard/index.ts +24 -6
- package/extensions/fpa-dashboard/publisher.ts +3 -2
- package/package.json +1 -1
- package/skills/fpa-forecast-approved-strategy/SKILL.md +3 -1
- package/skills/fpa-refresh-dashboard/SKILL.md +15 -4
- package/skills/fpa-refresh-dashboard/references/dashboard-policy.md +1 -1
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.
|
|
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.
|
|
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
|
-
|
|
100
|
-
|
|
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("
|
|
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({
|
|
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
|
|
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
|
@@ -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
|
|
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`,
|
|
30
|
-
|
|
31
|
-
unchanged.
|
|
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
|
|
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.
|