Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@

> 后续发布必须使用新的不可变 tag 与独立 ZIP;创建 Release、上传资产并完成重下 SHA-256 校验后,才可更新清单。不得使用 `latest/download`,不得复用已撤下的历史发布地址。

## 从 v0.1.x 迁移到 v0.2.0

v0.2.0 的主要变化:

1. **西方宫制规则版本**:默认 rulesetId 从 `western-tropical-placidus@0.1.0` 升级为 `western-tropical-placidus@0.2.0`。Koch 宫制计算已修正(旧版有最大 36° 偏差)。显式传入旧 `@0.1.0` 将返回 `RULESET_UNSUPPORTED` 错误,不会静默映射也不会继续计算错误结果。迁移方法:删除输入中的 `rulesetId` 字段(使用默认值),或显式改为 `western-tropical-placidus@0.2.0`。
2. **validate-answer 契约**:仅接受 `reading-draft/v2`;v1 返回 `UNSUPPORTED_CONTRACT_VERSION`。迁移方法见 `references/answer-contract.md`。
3. **引擎版本**:engineVersion 从 0.1.1 升至 0.2.0(Koch 宫制修正 + Swiss Ephemeris 独立金标覆盖)。

已发布的 v0.1.x Release 保持不变,作为历史可复现边界。

## ZIP 解压安全配额

所有宿主 Agent 在解压安装包时必须执行以下配额检查(基于 inflate 前的 central directory 元数据):
Expand Down
2 changes: 1 addition & 1 deletion docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ never by hand.
| Command | Result |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pnpm run typecheck` | clean (tsc strict over packages, tools, tests) |
| `pnpm run test` | 680 tests / 33 files �all passing (all systems + JPL Horizons 独立 golden + interpret + 吉凶 + 合婚 + reading-lint/空话/重复/越界 + validate-answer v2 结构与措辞门禁(约束引用事实豁免+全可见文本安全扫�资源上限+有界解析入口,非语义正确性证明) + western-rules/ziwei-rules 语义规则 + 版本迁移/回滚/目标白名�+ PII 隐私护栏 green) |
| `pnpm run test` | 685 tests / 34 files �all passing (all systems + JPL Horizons 独立 golden + interpret + 吉凶 + 合婚 + reading-lint/空话/重复/越界 + validate-answer v2 结构与措辞门禁(约束引用事实豁免+全可见文本安全扫�资源上限+有界解析入口,非语义正确性证明) + western-rules/ziwei-rules 语义规则 + 版本迁移/回滚/目标白名�+ PII 隐私护栏 green) |
| `pnpm run build` | `engine.mjs` ï¿?2.8 MB + `sbom.cdx.json` + `sbom.spdx.json` (6 runtime deps) |
| `pnpm run validate:skill` | 40 / 40 (incl. scripts/ no-stray-files guard + CycloneDX/SPDX SBOM checks + validate-answer/lint-reading gate-workflow doc checks) |
| `pnpm run validate:reading` | 53 / 53 (topic example libraries + output-spec structure + 无术语区 firewall; offline, no LLM) |
Expand Down
2 changes: 1 addition & 1 deletion docs/VALIDATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ the identical table in [STATUS.md](./STATUS.md) ("Commands & results"). Do not h
resolve a disagreement; re-run the suite and copy the actual count. `pnpm run check:doc-counts`
re-runs the suite and fails if either doc's `N tests / M files` count drifts from the real run.

- Typecheck: clean. Tests: **680 tests / 33 files ï¿?all passing**. The Western provider
- Typecheck: clean. Tests: **685 tests / 34 files ï¿?all passing**. The Western provider
(astronomy-engine, VSOP87 + NOVAS) passes the ADR-0003 ï¿?ï¿?gate two ways: wrapper-consistency
(vs astronomy-engine's own output) plus an **independent JPL Horizons golden** (10 bodies × 3
technical epochs fetched from the NASA/JPL Horizons service, query recorded in
Expand Down
5 changes: 4 additions & 1 deletion packages/contracts/src/birth-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ export const GeoLocation = z.strictObject({
export type GeoLocation = z.infer<typeof GeoLocation>;

/** Western defaults: tropical zodiac, Placidus houses, true node (handoff §5.1). */
export const WESTERN_RULESET_CURRENT = 'western-tropical-placidus@0.2.0';
/** Retired ruleset ids that are no longer computed (returns RULESET_UNSUPPORTED). */
export const WESTERN_RULESET_RETIRED = ['western-tropical-placidus@0.1.0'] as const;
export const WesternSettings = z.strictObject({
rulesetId: z.string().default('western-tropical-placidus@0.1.0'),
rulesetId: z.string().default(WESTERN_RULESET_CURRENT),
zodiac: z.enum(['tropical', 'sidereal']).default('tropical'),
/** Sidereal ayanamsha model; only applied when zodiac = 'sidereal'. */
ayanamsha: z.enum(['lahiri', 'fagan-bradley']).default('lahiri'),
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const SCHEMA_VERSION = '0.1.0';
* corrected to the accurate "astronomy-engine/VSOP87+NOVAS" attribution, which changes emitted
* provenance text (not any numeric result), hence a new engine version.
*/
export const ENGINE_VERSION = '0.1.1';
export const ENGINE_VERSION = '0.2.0';

/** Human-facing engine name. */
export const ENGINE_NAME = 'ming-engine';
Expand Down
18 changes: 17 additions & 1 deletion packages/western/src/western-provider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { WARNING_CODES, makeWarning } from '@ming/contracts';
import {
WARNING_CODES,
makeWarning,
ERROR_CODES,
EngineError,
WESTERN_RULESET_RETIRED,
} from '@ming/contracts';
import type {
EngineWarning,
NormalizedBirthData,
Expand Down Expand Up @@ -48,6 +54,16 @@ export function computeWestern(
normalized: NormalizedBirthData,
settings: WesternSettings,
): { result: WesternChartResult | null; warnings: EngineWarning[] } {
// Guard: retired ruleset versions are rejected with a stable error code.
// The caller explicitly requested a version that is no longer computed
// (Koch was corrected in 0.2.0; the old erroneous results are not reproducible).
if ((WESTERN_RULESET_RETIRED as readonly string[]).includes(settings.rulesetId)) {
throw new EngineError(
ERROR_CODES.RULESET_UNSUPPORTED,
`Ruleset "${settings.rulesetId}" has been retired. Migrate to western-tropical-placidus@0.2.0 (see INSTALL.md migration notes). Historical results from v0.1.x releases remain available in those published releases.`,
{ requestedRuleset: settings.rulesetId, currentRuleset: 'western-tropical-placidus@0.2.0' },
);
}
const warnings: EngineWarning[] = [];
const dateMs = normalized.utcInstantMs;
const latitude = normalized.location.latitude;
Expand Down
83 changes: 83 additions & 0 deletions packages/western/test/western-ruleset-version.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Tests for the western-tropical-placidus ruleset version lifecycle:
// - Default rulesetId is 0.2.0
// - Explicit 0.2.0 works
// - Retired 0.1.0 is rejected with RULESET_UNSUPPORTED
// - Error message structure is stable and does not leak internals
import { describe, expect, it } from 'vitest';
import {
ERROR_CODES,
EngineError,
WESTERN_RULESET_CURRENT,
WESTERN_RULESET_RETIRED,
WesternSettings,
} from '@ming/contracts';
import { computeWestern } from '../src/western-provider.ts';
import { NormalizedBirthData } from '@ming/contracts';

/** A minimal valid NormalizedBirthData for exercising the provider (synthetic). */
const SYNTHETIC_INPUT = NormalizedBirthData.parse({
schemaVersion: '0.1.0',
calendar: 'gregorian',
timeAccuracy: 'exact',
timeKnown: true,
localDate: '2000-01-01',
localTime: '20:00:00',
localCivilIso: '2000-01-01T20:00:00+08:00',
timezone: 'Asia/Shanghai',
timezoneOffsetMinutes: 480,
utcInstant: '2000-01-01T12:00:00.000Z',
utcInstantMs: Date.UTC(2000, 0, 1, 12, 0, 0),
ambiguity: { status: 'unambiguous', candidateCount: 1 },
location: { latitude: 30.0, longitude: 120.0, source: 'user' },
solar: null,
tzdb: { version: '2024a', source: 'moment-timezone' },
});

describe('western ruleset version lifecycle', () => {
it('default rulesetId is western-tropical-placidus@0.2.0', () => {
const parsed = WesternSettings.parse({});
expect(parsed.rulesetId).toBe('western-tropical-placidus@0.2.0');
expect(parsed.rulesetId).toBe(WESTERN_RULESET_CURRENT);
});

it('explicit western-tropical-placidus@0.2.0 computes successfully', () => {
const settings = WesternSettings.parse({ rulesetId: 'western-tropical-placidus@0.2.0' });
const { result } = computeWestern(SYNTHETIC_INPUT, settings);
expect(result).not.toBeNull();
expect(result!.rulesetId).toBe('western-tropical-placidus@0.2.0');
});

it('retired western-tropical-placidus@0.1.0 is stably rejected', () => {
const settings = WesternSettings.parse({ rulesetId: 'western-tropical-placidus@0.1.0' });
let thrown: unknown;
try {
computeWestern(SYNTHETIC_INPUT, settings);
} catch (e) {
thrown = e;
}
expect(thrown).toBeInstanceOf(EngineError);
const err = thrown as EngineError;
expect(err.code).toBe(ERROR_CODES.RULESET_UNSUPPORTED);
// Error includes migration guidance
expect(err.message).toContain('0.2.0');
expect(err.message).toContain('retired');
});

it('error from retired ruleset does not leak internal implementation details', () => {
const settings = WesternSettings.parse({ rulesetId: 'western-tropical-placidus@0.1.0' });
let message = '';
try {
computeWestern(SYNTHETIC_INPUT, settings);
} catch (e) {
message = (e as Error).message;
}
// Must not contain stack traces, file paths, or internal function names
expect(message).not.toMatch(/\.ts:|at \w+|node_modules/);
// Must not contain the raw internal array reference
expect(message).not.toContain('WESTERN_RULESET_RETIRED');
});

it('WESTERN_RULESET_RETIRED contains 0.1.0', () => {
expect(WESTERN_RULESET_RETIRED).toContain('western-tropical-placidus@0.1.0');
});
});
13 changes: 11 additions & 2 deletions skills/calculate-birth-charts/scripts/dist/engine.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25279,7 +25279,7 @@ var require_lib2 = __commonJS({

// packages/contracts/src/version.ts
var SCHEMA_VERSION = "0.1.0";
var ENGINE_VERSION = "0.1.1";
var ENGINE_VERSION = "0.2.0";
var ENGINE_NAME = "ming-engine";
var SUPPORTED_YEAR_MIN = 1901;
var SUPPORTED_YEAR_MAX = 2100;
Expand Down Expand Up @@ -39989,8 +39989,10 @@ var GeoLocation = external_exports.strictObject({
/** Where the coordinates came from; geocoder use requires separate consent. */
source: external_exports.enum(["user", "geocoder", "import"])
});
var WESTERN_RULESET_CURRENT = "western-tropical-placidus@0.2.0";
var WESTERN_RULESET_RETIRED = ["western-tropical-placidus@0.1.0"];
var WesternSettings = external_exports.strictObject({
rulesetId: external_exports.string().default("western-tropical-placidus@0.1.0"),
rulesetId: external_exports.string().default(WESTERN_RULESET_CURRENT),
zodiac: external_exports.enum(["tropical", "sidereal"]).default("tropical"),
/** Sidereal ayanamsha model; only applied when zodiac = 'sidereal'. */
ayanamsha: external_exports.enum(["lahiri", "fagan-bradley"]).default("lahiri"),
Expand Down Expand Up @@ -51617,6 +51619,13 @@ function round6(n) {
return Math.round(n * 1e6) / 1e6;
}
function computeWestern(normalized, settings) {
if (WESTERN_RULESET_RETIRED.includes(settings.rulesetId)) {
throw new EngineError(
ERROR_CODES.RULESET_UNSUPPORTED,
`Ruleset "${settings.rulesetId}" has been retired. Migrate to western-tropical-placidus@0.2.0 (see INSTALL.md migration notes). Historical results from v0.1.x releases remain available in those published releases.`,
{ requestedRuleset: settings.rulesetId, currentRuleset: "western-tropical-placidus@0.2.0" }
);
}
const warnings = [];
const dateMs = normalized.utcInstantMs;
const latitude = normalized.location.latitude;
Expand Down
4 changes: 2 additions & 2 deletions tools/lib/host-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ export const PUBLISHED_RELEASE_TAG: string | null = 'v0.1.6';

// --- CANDIDATE: the next unpublished build produced by package:hosts. ---
/** Engine semver of the candidate: same audited engine as the source root (no engine change pending). */
export const CANDIDATE_ENGINE_VERSION = '0.1.1';
export const CANDIDATE_ENGINE_VERSION = '0.2.0';
/** Install-package release version of the candidate (next packaging release over the 0.1.1 engine). */
export const CANDIDATE_RELEASE_VERSION = '0.1.7';
export const CANDIDATE_RELEASE_VERSION = '0.2.0';
/** Immutable tag the CANDIDATE build targets (never `latest/download`). */
export const CANDIDATE_RELEASE_TAG = `v${CANDIDATE_RELEASE_VERSION}`;
/** Sub-directory under `releases/` for the candidate build (gitignored). */
Expand Down
Loading