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
22 changes: 22 additions & 0 deletions .changeset/32842-advisory-user-text.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@objectstack/lint': patch
---

fix(lint): the connector, permission-set and form-view advisories a save returns carry a user sentence in every platform locale

Clause-②: no
Metadata-protocol: none

Three advisory rule ids that the save door returns now carry `userTextParams` (the connector, permission set, object, view and entry names their user sentence interpolates) and have a sentence in all eleven platform locales, each in a `*.user-text.ts` catalog beside its rule and merged into the one catalog `findingUserMessage` renders from:

- `connector-credential-literal` (connector write), in four arms: a credential in a header, in an MCP server's environment variable, in `credentialEnv`, and a stdio MCP server whose credential reaches nothing. The sentence names the connector and the header or variable the credential sits under, and never the credential itself.
- `permission-retired-lifecycle-residue` (permission write): names the permission set, the object and the retired key.
- `predicate-rhs-path-shaped`, its advisory bare-word arm (view write): names the form view, the word and the operator, and gives both readings with the supported spelling of each (the quoted word, or the field on the left compared with a quoted value).

Rule ids, `message` and `hint` are unchanged, and the refusal arm of `predicate-rhs-path-shaped` (a dotted path on the right) carries no params, so it renders no user sentence. The `lintConnectorCredentialLiterals` and `validateRetiredPermissionResidue` registry entries now keep `userTextParams` when they re-map their findings, so the publish gate's advisories carry them. Because the finding objects now carry `userTextParams`, `os validate --json` and `os build --json` show that key on these findings in `warnings`.

The other seven advisory ids in these rule files get no sentence, because the save door never returns them:

- `agent-authoring-withdrawn`, `default-agent-outside-roster`, `default-agent-legacy-alias` and `mapping-connector-source-unknown`: reference-integrity suite members on its default `flow` write type. An agent, app or mapping write dispatches none of them, and a flow write's snapshot carries no agents, apps or mappings.
- `ai-skill-tool-unresolved`: the same, and kept off the door on purpose by ruling 5791822697 (ADR-0109 Decision §3): a per-write snapshot cannot resolve a skill's tools truthfully.
- `security-external-owner-unmapped` and `sharing-rule-object-not-shareable`: their registry entries run on the command line only.
6 changes: 6 additions & 0 deletions packages/lint/src/authoring-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2078,6 +2078,9 @@ export const AUTHORING_RULES: readonly AuthoringRule[] = [
path: f.path,
message: f.message,
hint: f.hint,
// The user sentence's names ride through the re-map, so the save door
// can render this advisory in the request's language.
...(f.userTextParams !== undefined ? { userTextParams: f.userTextParams } : {}),
})),
},
// The spec-liveness loop on the author side: a property the ledger marks
Expand Down Expand Up @@ -2199,6 +2202,9 @@ export const AUTHORING_RULES: readonly AuthoringRule[] = [
path: f.path,
message: f.message,
hint: f.hint,
// The user sentence's names ride through the re-map, so the save door
// can render this advisory in the request's language.
...(f.userTextParams !== undefined ? { userTextParams: f.userTextParams } : {}),
})),
},
// A format like `{plan_no}{000}` makes the referenced field part of the
Expand Down
351 changes: 351 additions & 0 deletions packages/lint/src/finding-user-text.connector-permission-form.test.ts

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions packages/lint/src/finding-user-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ import { OBJECT_REFERENCES_USER_TEXT } from './validate-object-references.user-t
import { FORM_LAYOUT_USER_TEXT } from './validate-form-layout.user-text.js';
import { VISIBILITY_PREDICATES_USER_TEXT } from './validate-visibility-predicates.user-text.js';
import { PAGE_FIELD_BINDINGS_USER_TEXT } from './validate-page-field-bindings.user-text.js';
import { CONNECTOR_CREDENTIAL_LITERALS_USER_TEXT } from './lint-connector-credential-literals.user-text.js';
import { RETIRED_PERMISSION_RESIDUE_USER_TEXT } from './validate-retired-permission-residue.user-text.js';
import { PREDICATE_PATH_REFS_USER_TEXT } from './validate-predicate-path-refs.user-text.js';

/**
* The names a finding's user sentence interpolates — object, field and
Expand Down Expand Up @@ -187,6 +190,11 @@ export const FINDING_USER_TEXT_CATALOG: UserTextCatalog = mergeUserTextCatalogs(
FORM_LAYOUT_USER_TEXT,
VISIBILITY_PREDICATES_USER_TEXT,
PAGE_FIELD_BINDINGS_USER_TEXT,
// The connector, permission and form-view writes: the connector credential,
// retired permission key and form predicate advisories.
CONNECTOR_CREDENTIAL_LITERALS_USER_TEXT,
RETIRED_PERMISSION_RESIDUE_USER_TEXT,
PREDICATE_PATH_REFS_USER_TEXT,
);

/**
Expand Down
12 changes: 12 additions & 0 deletions packages/lint/src/lint-connector-credential-literals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@

import { isCredentialShapedLiteral, isCredentialShapedName } from './credential-literal.js';
import { recordsOf } from './object-graph.js';
import type { UserTextParams } from './finding-user-text.js';

type AnyRec = Record<string, unknown>;

Expand All @@ -88,6 +89,14 @@ export interface ConnectorCredentialLiteralFinding {
path: string;
message: string;
hint: string;
/**
* The names this finding's user sentence interpolates
* (`lint-connector-credential-literals.user-text.ts`): the connector, the
* entry's NAME when the finding is about a map entry, and its position
* (`header` · `env` · `credential-env` · `stdio-auth`). ⛔ Never the value,
* for the module note's reason; see `AuthoringFinding.userTextParams`.
*/
userTextParams?: UserTextParams;
}

/**
Expand Down Expand Up @@ -194,6 +203,7 @@ export function lintConnectorCredentialLiterals(stack: AnyRec): ConnectorCredent
// `os explain connector-credential-literal`.
message: `${judged.entry} '${key}' in ${label} holds a credential-shaped literal, ${KEPT}; ${steer}`,
hint: judged.entry === 'env' ? ENV_ROUTE : HEADER_ROUTE,
userTextParams: { connector: name, name: key, position: judged.entry },
});
}
}
Expand All @@ -213,6 +223,7 @@ export function lintConnectorCredentialLiterals(stack: AnyRec): ConnectorCredent
'providerConfig.transport.credentialEnv reads as a credential, not a variable name, and is served on ' +
'every metadata read; put the secret behind `auth.credentialRef` and name its variable here',
hint: CREDENTIAL_ENV_ROUTE,
userTextParams: { connector: name, position: 'credential-env' },
});
}

Expand All @@ -234,6 +245,7 @@ export function lintConnectorCredentialLiterals(stack: AnyRec): ConnectorCredent
`auth '${auth.type}' reaches nothing: a stdio transport passes the resolved credential to the server only ` +
'in the variable `transport.credentialEnv` names, and this entry names none',
hint: STDIO_AUTH_UNPLACED_ROUTE,
userTextParams: { connector: name, position: 'stdio-auth' },
});
}
});
Expand Down
Loading
Loading