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
19 changes: 19 additions & 0 deletions .changeset/20752-cli-strings-state-the-decision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@objectstack/cli': patch
'@objectstack/types': patch
---

CLI help, warnings and refusals no longer cite tracker numbers; each one states the decision behind it in words

Clause-②: no

Several lines the CLI prints sent the reader to an issue-tracker number for the reason behind them. The number goes; where the sentence did not already say what was decided, it now does.

- `os build` / `os validate`: the provider preflight step now reads "Checking that every required capability has a provider installable in this edition...", and the undeclared-key header reads "Undeclared authoring keys (N) — dropped at load; reported here, never refused".
- `os doctor`: the retired `referenceFilters` row now says the key was removed from FieldSchema as a key no runtime read; the `NODE_ENV` and config-load rows drop their citations.
- `os serve`: the no-auth refusal says anonymous data access is always denied with no setting that turns that off; the organizations remedies drop their citations.
- `os meta resync`, `os db clean` and the `os migrate duplicates` / `multi-value-columns` / `recorded-by` / `summary-nulls` descriptions, the `os dev --restart` flag help, the `os storage orphans` closing line and the storage-driver refusals each say what was decided instead of citing it.
- `os serve`'s unknown-hostname 404 page spells its three short grey colours in six hex digits; they render the same.
- `@objectstack/types`: the host importer's undeclared-package message says the fallback resolves from the caller once `fallbackImport` is passed, and drops the citation beside "Being merely REACHABLE is not enough".

Text only: no exit code, error code, flag, field or control flow moves. A script that matches the old CLI text (for example the "Checking capability providers" step line) needs the new spelling.
2 changes: 1 addition & 1 deletion content/docs/deployment/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ os compile --json # JSON output for CI pipelines
→ Lowering inline handlers...
→ Validating protocol compliance...
→ Running author-time rules (48)...
→ Checking capability providers (#3366)...
→ Checking that every required capability has a provider installable in this edition...
→ Collecting package docs (ADR-0046)... 0 collected
→ Writing artifact...

Expand Down
2 changes: 1 addition & 1 deletion content/docs/deployment/validating-metadata.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ A clean run walks the registry and reports timing:
Load time: 21ms
→ Validating against ObjectStack Protocol...
→ Running author-time rules (48)...
→ Checking capability providers (#3366)...
→ Checking that every required capability has a provider installable in this edition...
→ Checking package docs (ADR-0046)...

✓ Validation passed (64ms)
Expand Down
2 changes: 1 addition & 1 deletion content/docs/ui/react-pages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ objectstack validate
→ Loading configuration...
→ Validating against ObjectStack Protocol...
→ Running author-time rules (48)...
→ Checking capability providers (#3366)...
→ Checking that every required capability has a provider installable in this edition...
→ Checking package docs (ADR-0046)...

✓ Validation passed (1523ms)
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,7 @@ export default class Compile extends Command {
// `os start` crash. Absent-but-installable is a `pnpm add` hint.
//
// Not a registry rule: it reads `node_modules`, not the stack.
if (!flags.json) printStep('Checking capability providers (#3366)...');
if (!flags.json) printStep('Checking that every required capability has a provider installable in this edition...');
const capPreflight = preflightRequiredCapabilities({
requires: Array.isArray((config as { requires?: unknown[] }).requires)
? ((config as { requires?: unknown[] }).requires as unknown[])
Expand Down Expand Up @@ -821,7 +821,7 @@ export default class Compile extends Command {
...lintUnknownAuthoringKeys(normalized as Record<string, unknown>, ObjectStackDefinitionSchema),
].map(formatUnknownAuthoringKey);
if (unknownKeyWarnings.length > 0 && !flags.json) {
printWarning(`Undeclared authoring keys (${unknownKeyWarnings.length}) — dropped at load (#3786)`);
printWarning(`Undeclared authoring keys (${unknownKeyWarnings.length}) — dropped at load; reported here, never refused`);
// [#11642] The header already states the true total, so before this
// notice the block printed two numbers that disagreed and explained
// neither. The pointer resolves because #11643 put this exact list
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/db/clean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default class DbClean extends Command {
static override flags = {
database: Flags.string({
char: 'd',
description: 'SQLite database URL/path (defaults to $OS_DATABASE_URL, then the project database via the shared #6469 resolution)',
description: 'SQLite database URL/path (defaults to $OS_DATABASE_URL, then the one project database that os dev, os start and os migrate all resolve)',
env: 'OS_DATABASE_URL',
}),
};
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export default class Dev extends Command {
}),
restart: Flags.boolean({
description:
'Restart the server after each successful rebuild so the running server always matches dist/objectstack.json (#5148). With --no-restart the watcher only rebuilds the artifact — the running server keeps the build it booted with until you restart it yourself, and every rebuild says so.',
'Restart the server after each successful rebuild so the running server always matches dist/objectstack.json. With --no-restart the watcher only rebuilds the artifact — the running server keeps the build it booted with until you restart it yourself, and every rebuild says so.',
default: true,
allowNo: true,
}),
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export function nodeEnvCheck(env: NodeJS.ProcessEnv = process.env): HealthCheckR
+ ' • local development → NODE_ENV=development (what `os dev` already sets)\n'
+ ' Unset reads as production everywhere: `os serve` and `os doctor` resolve the\n'
+ ' `.env*` cascade for node_env=production, and the /discovery `environment` field\n'
+ ' advertises "production" (#5673). That is the safe direction — a client asking\n'
+ ' advertises "production". That is the safe direction — a client asking\n'
+ ' "am I talking to production?" is never told "development" by an omission — but\n'
+ ' it also makes an oversight look identical to a deliberate production deployment,\n'
+ ' and this row is the only place the difference is visible.\n'
Expand Down Expand Up @@ -1403,7 +1403,7 @@ const DEPRECATED_PATTERNS: Array<{
// Pointing at the removed key sent authors to a silently-stripped spelling.
pattern: /\breference_filters\b|\breferenceFilters\b/,
description: 'retired lookup-scoping key: reference_filters / referenceFilters',
replacement: 'Use lookupFilters (camelCase) — `referenceFilters` was removed in #2377',
replacement: 'Use lookupFilters (camelCase) — `referenceFilters` was removed from FieldSchema as a key no runtime read (ADR-0049 enforce-or-remove)',
},
{
pattern: /\bunique_name\b/,
Expand Down Expand Up @@ -1541,7 +1541,7 @@ export function configLoadFailureCheck(err: unknown): HealthCheckResult {
message: `Could not load config for analysis (config checks skipped) — ${reportRowHeadline(cause)}`,
fix:
'`os serve` loads this same file the same way — bundle-require, under the `.env*`\n'
+ ' cascade named above (#5397) — and prints this error in full, so a config that\n'
+ ' cascade named above — and prints this error in full, so a config that\n'
+ ' lands here is one the server cannot boot either.\n'
+ ' The config-aware checks were SKIPPED, not passed: platform protocol,\n'
+ ' circular dependencies, unused objects, orphan views, dashboard integrity.\n'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('resyncSkipExplanationLine — the runtime-output half of #9184', () =>
expect(line).not.toBeNull();
expect(line).toContain('admin');
expect(line).toContain('user');
expect(line).toContain('#8692');
expect(line).toContain("before the seeder began stamping its default sets 'platform'");
});

it('fires identically for a PARTIAL skip — same trigger as the summary, not `resynced === 0`', () => {
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/commands/meta/resync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ export function resyncSkipExplanationLine(resyncSkipped: number): string | null
return (
" Expected, not a failure — resync only reconciles platform-owned rows. " +
"A stored 'admin' stamp (or the legacy 'user' spelling) isn't always a deliberate Setup takeover: " +
"on installs from before #8692, the platform's own seeded defaults carry that same stamp, so a " +
"on installs created before the seeder began stamping its default sets 'platform', the platform's own " +
"seeded defaults carry that same stamp, so a " +
"persistent skip count here can be permanent by design. A package-owned row, by contrast, is " +
"always a deliberate override by the package that owns it."
);
Expand Down Expand Up @@ -100,7 +101,7 @@ export function resyncSkipExplanationLine(resyncSkipped: number): string | null
*/
export default class MetaResync extends Command {
static override description =
'Reconcile materialized metadata (default permission sets) to the compiled dist without a --fresh wipe (#2705)';
'Reconcile materialized metadata (default permission sets, which boot seeds insert-once) to the compiled dist without a --fresh wipe';

static override examples = [
'$ os meta resync',
Expand Down
7 changes: 4 additions & 3 deletions packages/cli/src/commands/migrate/duplicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -855,10 +855,11 @@ export async function collectDuplicateIdentifierReport(

export default class MigrateDuplicates extends Command {
static override description =
'Report business identifiers already minted twice across the organization partitions (#8928), ' +
'and the rows blocking the kernel:ready NULL-safe index tightenings (#8725). ' +
'Report business identifiers already minted twice across the organization partitions, ' +
'and the rows blocking the kernel:ready NULL-safe index tightenings, which os migrate plan does not report. ' +
'Read-only inventory as JSON on stdout — never renumbers, deduplicates or rewrites anything. ' +
'Run it BEFORE the #8686 tenancy backfill: the repair overwrites the evidence.';
"Run it BEFORE the boot-time backfill that stamps untenanted seed rows with the install's organization: " +
'the repair overwrites the evidence.';

static override examples = [
'$ os migrate duplicates',
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/commands/migrate/multi-value-columns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,8 @@ async function confirm(question: string): Promise<boolean> {
*/
export default class MigrateMultiValueColumns extends Command {
static override description =
'Migrate a stale varchar/text column to json where the field declares multiple: true (#11535). ' +
'Migrate a stale varchar/text column to json where the field declares multiple: true; boot and ' +
'os migrate plan only report such a column and never alter it unattended. ' +
'Dry-run by default: prints the exact statements and the database they would run against, and writes nothing.';

static override examples = [
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/commands/migrate/recorded-by.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ async function confirm(question: string): Promise<boolean> {
*/
export default class MigrateRecordedBy extends Command {
static override description =
"Rewrite the legacy 'system' sentinel in sys_metadata_history.recorded_by to NULL (#4556). " +
"Rewrite the legacy 'system' sentinel in sys_metadata_history.recorded_by to NULL, the value a " +
'system-initiated write stores now. ' +
'Dry-run by default; --apply runs the conversion through the migration journal.';

static override examples = [
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/commands/migrate/summary-nulls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ async function confirm(question: string): Promise<boolean> {
export default class MigrateSummaryNulls extends Command {
static override description =
'Backfill roll-up count/sum summary columns still stored as NULL on parent rows created before the ' +
'insert-time seed (#5749). Dry-run by default; --apply recomputes and writes each affected row.';
'insert-time seed, which now writes 0 for an empty child set. ' +
'Dry-run by default; --apply recomputes and writes each affected row.';

static override examples = [
'$ os migrate summary-nulls',
Expand Down
14 changes: 7 additions & 7 deletions packages/cli/src/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4390,7 +4390,7 @@ export default class Serve extends Command {
if (flags.server && !(tierEnabled('auth') || hasAuthPlugin)) {
throw new Error(
'This stack mounts no auth, so no caller can authenticate — and anonymous access to object '
+ 'data is always denied (#3963), which would leave the data API unusable.\n'
+ 'data is always denied, with no setting that turns that off, which would leave the data API unusable.\n'
+ 'Fix it one of two ways:\n'
+ ` • enable auth — add the 'auth' tier (or mount AuthPlugin in \`plugins\`);\n`
+ ' • or serve without the data API — run with --no-server, or drop the REST/dispatcher plugins.\n'
Expand Down Expand Up @@ -5629,7 +5629,7 @@ export function createUnknownHostnameGuardPlugin(
body {
font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: #fafafa;
color: #111;
color: #111111;
display: grid;
place-items: center;
padding: 24px;
Expand All @@ -5654,7 +5654,7 @@ export function createUnknownHostnameGuardPlugin(
.code { font: 600 64px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; margin: 0; letter-spacing: -2px; }
h1 { font-size: 20px; margin: 16px 0 8px; font-weight: 600; }
p { margin: 8px 0; }
.muted { color: #666; font-size: 14px; }
.muted { color: #666666; font-size: 14px; }
.host {
display: inline-block;
margin-top: 16px;
Expand All @@ -5663,7 +5663,7 @@ export function createUnknownHostnameGuardPlugin(
border: 1px solid #e4e4e7;
border-radius: 6px;
font: 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
color: #444;
color: #444444;
word-break: break-all;
}
a { color: #2563eb; text-decoration: none; }
Expand Down Expand Up @@ -5949,8 +5949,8 @@ export function formatOrganizationsInstallRemedy(
return ` • add ${pkg} (the multi-org runtime) to THIS APP\n` +
" — declare it in the app's package.json and install; the CLI resolves it from the\n" +
' app, not from the framework it is linked out of. Being merely reachable\n' +
' through NODE_PATH / a hoisted workspace store is deliberately not enough\n' +
' (#4719) — that made this wall depend on how the process was launched.\n' +
' through NODE_PATH / a hoisted workspace store is deliberately not enough:\n' +
' accepting it made this wall depend on how the process was launched.\n' +
' NOTE: this runtime is Apache-2.0 and published on the public npm registry\n' +
' (ADR-0132), so this bullet is followable on any install — no subscription.\n' +
' A commercial deployment resolves the same package name to its own private,\n' +
Expand Down Expand Up @@ -6018,7 +6018,7 @@ export function formatOrganizationsMountFatal(
" • set OS_TENANCY_POSTURE=single (or unset OS_MULTI_ORG_ENABLED) to run single-org.\n\n" +
' OS_ALLOW_DEGRADED_TENANCY does NOT apply to this failure and will not get past it:\n' +
' it covers an ABSENT multi-org runtime the operator accepts doing without, not a\n' +
' present one that declined to mount. (#4818)\n'
' present one that declined to mount.\n'
);
}

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/storage/orphans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ export default class StorageOrphans extends Command {
printSuccess('Nothing stranded on this deployment.');
} else {
printInfo(
'Reporting only. Reclaiming these bytes is a separate, deliberately deferred decision — ' +
'see issue #10950.',
'Reporting only. Reclaiming these bytes was decided against: deletes no longer strand files, ' +
'and files stranded before that fix are left in place.',
);
}
console.log(chalk.dim(` ${timer.display()}`));
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ export default class Validate extends Command {
);
}

if (!flags.json) printStep('Checking capability providers (#3366)...');
if (!flags.json) printStep('Checking that every required capability has a provider installable in this edition...');
const capProviderPreflight = preflightRequiredCapabilities({
requires: Array.isArray((config as { requires?: unknown[] }).requires)
? ((config as { requires?: unknown[] }).requires as unknown[])
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/utils/i18n-extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2351,7 +2351,7 @@ export function renderSourceHashModule(
lines.push(' *');
lines.push(" * Each entry is the digest of the SOURCE REVISION that this locale's leaf at");
lines.push(' * that path is still a byte copy of — provenance for the generated half of the');
lines.push(' * bundles (commit 09b4f4e4e, maintainer ruling #12069 Option A, extending #8765 Option B).');
lines.push(' * bundles (commit 09b4f4e4e): a leaf whose digest no longer matches its source is stale and serves the source text instead.');
lines.push(' *');
lines.push(' * An entry exists only while the leaf IS such a copy. Re-translate the leaf in');
lines.push(' * `<locale>.objects.generated.ts` and the next extract drops its entry by');
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/utils/storage-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ function missingUrlMessage(kind: BuiltinDriverId): string {
+ `URL was given, and ${kind} has no local default to fall back on — its database lives on a `
+ 'server or endpoint this process cannot guess. Set OS_DATABASE_URL (or --database) to it — '
+ `e.g. ${example}. Booting on a guessed default instead would connect you `
+ 'to a database you never named, and every write would land in the wrong place (#3276).'
+ 'to a database you never named, and every write would land in the wrong place.'
);
}

Expand Down Expand Up @@ -346,7 +346,7 @@ export function resolveStorageDefinition(
`Unsupported driver "${driverType}" (OS_DATABASE_DRIVER / --database-driver). `
+ `Supported drivers: ${DATABASE_DRIVER_SELECTION_ALIASES.join(', ')}. `
+ 'Booting on the SQLite default instead would silently ignore the driver you asked for '
+ 'and write into a local database (#3276). Fix the value, or leave the driver unset to '
+ 'and write into a local database. Fix the value, or leave the driver unset to '
+ 'let the database URL scheme select it.',
// NOT a driver kind — `driverType` here is the operator's raw token, and a
// caller enumerating kinds must not count it as one.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ describe('#11643 — `os build --json` carries the undeclared-authoring-key warn
// consumed by both faces. The text output must be what it was.
const run = await runCli(['build'], dirs.planted);
expect(run.code, `os build failed:\n${run.stdout}${run.stderr}`).toBe(0);
expect(run.stdout).toContain('Undeclared authoring keys (1) — dropped at load (#3786)');
expect(run.stdout).toContain('Undeclared authoring keys (1) — dropped at load; reported here, never refused');
expect(run.stdout).toContain(`${PLANTED_KEY}' is not a declared field key, so its value is dropped at load.`);
}, 120_000);
});
Loading
Loading