Skip to content

Feature ETP-4793: Persist field visibility, F23 validator rule - #128

Open
valenvivaldi wants to merge 11 commits into
epic/ETP-3504from
feature/ETP-4793
Open

Feature ETP-4793: Persist field visibility, F23 validator rule#128
valenvivaldi wants to merge 11 commits into
epic/ETP-3504from
feature/ETP-4793

Conversation

@valenvivaldi

Copy link
Copy Markdown
Collaborator

Tooling-side (published packages) support for the field-visibility and entity-exclusion fixes surfaced by the MCP benchmark, plus preview-publishing pipeline hardening.

  • Persist curated field visibility on push to NEO (cli/src/neo-writer.js, cli/src/push-to-neo.js, cli/src/lib/field-visibility.js)
  • Reflect entity exclude:true as ISINCLUDED='N' in NEO (cli/src/lib/entity-methods.js, cli/src/lib/neo-delta.js); explicit comparator for the excluded-entity sort
  • Add validator rule F23 for pushed field visibility (cli/src/validate-pipeline.js) with fixture matrix (clean/blank/aggregate/contradiction/inactive/unwritten/empty sourcedata)
  • Publish preview packages without requiring an open PR (.github/workflows/publish-preview.yml)
  • New/expanded tests: field-visibility.test.js, neo-writer-populate.test.js, neo-writer-upsert-field.test.js, push-to-neo-helpers.test.js, validate-pipeline.test.js, entity-methods-pipeline.test.js
  • Update docs/decisions-reference.md and docs/pipeline-validator-reference.md for F23

Jira

ETP-4793, epic ETP-3504

The preview job was gated on the branch having an open PR, so a push to a
PR-less feature branch ended green after the lookup with no tests, no publish
and no cleanup — see run 31113050482 on feature/ETP-4793.

The gate was in the wrong place. What consumes a preview is
'make bump-core-version VERSION=...', which needs the published version and
nothing else; the PR was never a technical requirement. And a branch is often
worth previewing before it is ready for review, which is exactly when the
published-package path most needs exercising.

find-pr no longer decides anything: it looks the PR up, and its only consumer
is the sticky-comment step, now gated at step level. Without a PR the publish
happens and the version is read off the 'preview-package' commit status, which
is the surface that always exists. Nothing else depended on the PR —
preview-version.mjs and cleanup-preview-packages.mjs both derive everything
from the branch id.
IMP-11. ETGO_SF_FIELD.VISIBILITY existed in the DDL, the model and the
generated entity class, and McpSchemaFieldBuilder already serialized it — but
no writer ever populated it, so all 6340 rows were NULL and neo_schema never
emitted the key. Two independent gaps closed:

- neo-writer.js upsertField: the column was absent from the INSERT list and
  from the partial UPDATE. Added, routed through a new normalizeVisibility()
  that rejects values outside the curated vocabulary instead of storing
  garbage the reader would then serve as truth. NULL stays legal and means
  'not classified' — populateSpec creates a row per AD column before any
  contract is applied.
- push-to-neo.js buildFieldUpdateParams: had f.visibility in hand and
  discarded it. Now forwarded alongside the mapVisibility pair, not instead
  of it. isIncluded/isReadOnly are unchanged, so NEO runtime behaviour is
  untouched. reportDryRunPlan mirrors it so --dry-run stops lying.

mapVisibility collapses four curated values into two booleans: system and
readOnly both map to Y/Y, discarded and unknown both to N/N. That collapse is
what the runtime wants and is also why the curated value must travel
separately — the agent-facing hint tells agents to skip system fields and
display readOnly ones, which Y/Y cannot express.

Rewrote the two agent_prompt INSERT assertions that asserted 'the last param':
positional assertions silently move onto the wrong column when a column is
appended. They now resolve the index from the SQL column list.
`exclude: true` only ever removed the entity from contract.json. Both write
paths derive the ETGO_SF_ENTITY rows from ad_tab, never from the contract, so
an excluded entity still got a row with ISINCLUDED='Y' plus one field row per
AD column — 90 entities and 386 fields served on the reference instance, and
served with MORE verbs than their curated siblings, because a tab with no
contract entity falls through to the window-level method default.

Adds one shared predicate, isEntityExcludedFromContract(), in the file both
write paths already share, and threads it through:

- push-to-neo: the method-flags resolver now also answers isIncluded, and the
  dry-run plan reports the entities it will close.
- neo-writer: closes the entity and, with it, every field row underneath —
  redundant for behaviour (every reader filters the entity first) but field
  rows claiming 'Y' are what kept the gap invisible.
- neo-delta: same flip on the predicted XML rows, applied BEFORE the
  entity-blind flat-column rule that let an excluded entity's column survive
  whenever a sibling contract entity had a column of the same name.

The six method flags stay at the window default rather than being zeroed: they
are unreachable on a closed entity, and an all-N set would break the
GET/GETBYID invariant entity-methods.js enforces. VISIBILITY is left NULL —
the XML delta does not model that column, and NULL beside 'N'/'N' is already
the pair mapVisibility('discarded') produces.

No schema change, no new AD records: ISINCLUDED already existed on both tables
and 24 call sites across the REST and MCP surfaces already filter on it.
Sonar javascript:S2871 — Array.prototype.sort() with no compare function
coerces elements to strings, so it only happens to be right for an array of
names. Sorting entity names with localeCompare is what the rest of the CLI
does (check-window-docs.js, check-version.js) and is behaviour-preserving
here.
IMP-26 §5.3 asked for a recurrence guard on the ETGO_SF_FIELD invariant
mapVisibility(visibility) == (isincluded, isreadonly). F23 reads the exported
ETGO_SF_*.xml sourcedata (the only DB-free view of pushed state) and re-runs
the projection over it.

Two classes, scored differently: BLOCK when a curated VISIBILITY projects to a
different flag pair than the one stored (writer bug or hand-edit), WARN when
VISIBILITY was never written while the flags say included (neo_schema then
reports no visibility, so an agent cannot tell readOnly from system). One
BLOCKing rule would have gone red on 409 rows on day one. An absent VISIBILITY
on a closed row is coherent, not debt: N/N is mapVisibility(null), which is the
shape the exclude: true fix produces.

Registered for window AND aggregate artifacts. Window-only reported 69 of the
409 incoherent rows in the live export; the other 340 sit in two aggregates.

mapVisibility moves to lib/field-visibility.js. It existed twice (exported from
push-to-neo, inlined in neo-delta to dodge a circular import) and F23 would
have been a third copy — a validator that re-implements the projection cannot
detect a drift in the projection. push-to-neo re-exports it as public API.

Measured against the live DB and the XML independently, agreeing: 0
contradictions, 409 unwritten of 6,468 active rows, in 6 specs.
ruleF23 and runWindowChecks had a defaulted parameter followed by a
non-defaulted one. Reordered the signatures and updated all internal
call sites. Behavior unchanged.
@github-actions

Copy link
Copy Markdown
Contributor

Copilot PR Review

Outcome: Request changes
Blocking findings must be resolved before merge.

Blocking findings

  • Duplicated added block (DUPLICATED_BLOCK)
    The same normalized block of 10 added lines appears in multiple places. Extract shared logic or remove the copy-paste.

  • cli/test/fixtures/f23-sourcedata-blank/ETGO_SF_ENTITY.xml:1-10

  • cli/test/fixtures/f23-sourcedata-clean/ETGO_SF_ENTITY.xml:1-10

  • cli/test/fixtures/f23-sourcedata-contradiction/ETGO_SF_ENTITY.xml:1-10

  • cli/test/fixtures/f23-sourcedata-inactive/ETGO_SF_ENTITY.xml:1-10

  • cli/test/fixtures/f23-sourcedata-unwritten/ETGO_SF_ENTITY.xml:1-10

  • Duplicated added block (DUPLICATED_BLOCK)
    The same normalized block of 9 added lines appears in multiple places. Extract shared logic or remove the copy-paste.

  • cli/test/fixtures/f23-sourcedata-blank/ETGO_SF_SPEC.xml:1-9

  • cli/test/fixtures/f23-sourcedata-clean/ETGO_SF_SPEC.xml:1-9

  • cli/test/fixtures/f23-sourcedata-contradiction/ETGO_SF_SPEC.xml:1-9

  • cli/test/fixtures/f23-sourcedata-inactive/ETGO_SF_SPEC.xml:1-9

  • cli/test/fixtures/f23-sourcedata-unwritten/ETGO_SF_SPEC.xml:1-9

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot PR Review

Outcome: Request changes
Blocking findings must be resolved before merge.

Blocking findings

  • Duplicated added block (DUPLICATED_BLOCK)
    The same normalized block of 10 added lines appears in multiple places. Extract shared logic or remove the copy-paste.

  • cli/test/fixtures/f23-sourcedata-blank/ETGO_SF_ENTITY.xml:1-10

  • cli/test/fixtures/f23-sourcedata-clean/ETGO_SF_ENTITY.xml:1-10

  • cli/test/fixtures/f23-sourcedata-contradiction/ETGO_SF_ENTITY.xml:1-10

  • cli/test/fixtures/f23-sourcedata-inactive/ETGO_SF_ENTITY.xml:1-10

  • cli/test/fixtures/f23-sourcedata-unwritten/ETGO_SF_ENTITY.xml:1-10

  • Duplicated added block (DUPLICATED_BLOCK)
    The same normalized block of 9 added lines appears in multiple places. Extract shared logic or remove the copy-paste.

  • cli/test/fixtures/f23-sourcedata-blank/ETGO_SF_SPEC.xml:1-9

  • cli/test/fixtures/f23-sourcedata-clean/ETGO_SF_SPEC.xml:1-9

  • cli/test/fixtures/f23-sourcedata-contradiction/ETGO_SF_SPEC.xml:1-9

  • cli/test/fixtures/f23-sourcedata-inactive/ETGO_SF_SPEC.xml:1-9

  • cli/test/fixtures/f23-sourcedata-unwritten/ETGO_SF_SPEC.xml:1-9

@sonarscanetendo

Copy link
Copy Markdown

Passed Quality Gate passed

Issues

Measures

Project ID: etendosoftware_schema_forge_core_976a1e0b-4b24-4757-a2f4-8afa9a5e0289

View in SonarQube

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant