Skip to content

validator: introduce mode-aware constructors and validation API#1177

Open
teresaromero wants to merge 30 commits into
elastic:mainfrom
teresaromero:549-validation-api
Open

validator: introduce mode-aware constructors and validation API#1177
teresaromero wants to merge 30 commits into
elastic:mainfrom
teresaromero:549-validation-api

Conversation

@teresaromero

@teresaromero teresaromero commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Introduces a mode-aware validation API in pkg/validator and wires validation mode through the internal Spec.

Public API (code/go/pkg/validator/validator.go)

  • Adds Mode (re-exported from internal) with three values: ModeLegacy, ModeSource, and ModeBuild.
  • Adds Validator configured via NewValidator(mode Mode, opts ...Option) (*Validator, error).
  • Adds functional option WithWarningsAsErrors(bool) to control warning promotion independently of the PACKAGE_SPEC_WARNINGS_AS_ERRORS environment variable.
  • Adds mode-aware methods on *Validator:
    • ValidateFromPath(path string) — wraps the disk FS per mode (linkedfiles.NewFS for legacy/source, linkedfiles.NewBlockFS for build).
    • ValidateFromZip(zipPath string) — supported in ModeLegacy and ModeBuild only; always validates through BlockFS.
    • ValidateFromFS(location string, fsys fs.FS) — validates caller-supplied FS with mode-specific linked-file semantics and rejects incompatible FS/mode pairings (e.g. linkedfiles.FS in build mode, BlockFS in source mode).
  • Deprecates the existing package-level functions ValidateFromPath, ValidateFromZip, and ValidateFromFS; each now delegates to NewValidator(ModeLegacy) and preserves legacy behavior for existing callers.

Internal plumbing (code/go/internal/validator/)

  • Adds Mode type and Valid() in modes.go.
  • Extends NewSpec(version, mode) and stores mode on Spec.
  • Extends Spec.rules() with an optional per-rule modes []Mode filter so follow-up PRs can gate semantic rules without further API changes.

Changelog

  • Adds a 3.7.0-next entry for mode-aware constructors and validation APIs.

Invalid mode values (including zero-value Mode("")) are rejected at NewValidator construction time.

Why is it important?

The validator currently has no concept of validation mode — all callers get the same rules regardless of whether they are validating a source checkout or a built artifact. This PR lays the API foundation so future PRs can attach mode-specific rules (and enforce distinct source vs. build package shapes per #549) without breaking existing callers.

Existing integrations (elastic-package, etc.) continue to work via the deprecated entry points, which run in ModeLegacy.

Checklist

Related issues

Tests

New and updated tests cover the mode-aware API:

Test File What it verifies
TestValid internal/validator/modes_test.go Mode.Valid() accepts legacy/source/build and rejects invalid/empty strings
TestNewValidator_RejectsInvalidMode pkg/validator/validator_test.go Invalid mode strings are rejected at construction
TestLinksBehaviorAcrossModes pkg/validator/validator_test.go Build rejects .link files; source and legacy accept them on disk
TestValidateFromFS_rejectsIncompatibleFS pkg/validator/validator_test.go Build + linkedfiles.FS and source + BlockFS are rejected; compatible pairings succeed
TestValidateFromFS_legacyPlainFSBlocksLinks pkg/validator/validator_test.go Legacy auto-wraps plain FS with BlockFS; legacy + linkedfiles.FS resolves links
TestValidateFromZip_modeRestrictions pkg/validator/validator_test.go Zip validation rejected in source mode; allowed in legacy and build
TestValidateFromZip_validatesPackage pkg/validator/validator_test.go Valid zip succeeds; malformed zip root layout errors
TestDeprecatedValidateFromZip pkg/validator/validator_test.go Deprecated ValidateFromZip wrapper still works
TestWithWarningsAsErrors_option pkg/validator/validator_test.go Option overrides env var for warning promotion
TestLinksAreBlocked pkg/validator/validator_test.go Deprecated ValidateFromFS with plain mock FS blocks links (legacy behavior)

Uses existing test package test/packages/with_links for link-file scenarios. Internal spec_test.go updated for NewSpec(..., ModeLegacy).

Follow-ups / open questions

  • ModeLegacy vs ModeSource: kept as distinct modes for migration safety; may collapse once source-only semantics are fully defined and callers have migrated.
  • Mode-gated semantic rules: infrastructure is in place (modes []Mode on rule registration); individual rules are not yet filtered — that lands in a follow-up PR.
  • Caller migration: elastic-package and other consumers still use deprecated entry points; reference migration to NewValidator(ModeSource|ModeBuild) is planned separately.

teresaromero and others added 8 commits June 1, 2026 15:53
Introduce a modes.Mode enum (Legacy/Source/Build) and wire it into
the Spec struct and rulesDef filter loop. Defaulting to Legacy
reproduces today's behavior exactly — no rules are re-tagged yet.

- New package: code/go/internal/validator/modes
- NewSpec now accepts a mode parameter (defaults to Legacy if empty)
- rulesDef gains a modes field; nil means "applies in all modes"
- Filter loop skips rules whose modes list excludes the current mode

All existing tests pass unchanged. No fixture or semantic rule changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add validator.Mode type alias re-exporting internal modes.Mode so
  callers can write validator.ModeLegacy / ModeSource / ModeBuild.
- Add Validator struct with NewFromPath, NewFromZip, NewFromFS
  constructors and a Validate method.
- Add WithWarningsAsErrors functional option.
- Rewrite ValidateFromPath/Zip/FS as 3-line wrappers that delegate to
  NewFromX(ModeLegacy, ...) — zero behaviour change.
- Add parametrised legacy-preservation tests across all ~180 fixtures
  for each constructor path, plus a zip golden test (first zip coverage
  in this repo).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both constructors previously returned a nil error unconditionally; they now
validate the root path/filesystem at construction time so callers get a clear
error immediately rather than a confusing failure deep inside Validate().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dfiles.NewFS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… handling

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NewFromZip no longer accepts a mode parameter (always ModeBuild); update the
test to use the new signature as a constructor smoke test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@teresaromero teresaromero requested a review from a team as a code owner June 2, 2026 06:53
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a validation mode system (legacy/source/build), exposes mode-aware public validator constructors and an instance-based Validate(), embeds mode into Spec with rule filtering by mode, updates tests to exercise mode behavior (including link-file handling), and records an enhancement in the changelog.

Changes

Validation Mode System

Layer / File(s) Summary
Mode type definition and tests
code/go/internal/validator/modes/modes.go, code/go/internal/validator/modes/modes_test.go
Introduce modes.Mode string type, Legacy/Source/Build constants, Valid() method, and table-driven tests for valid/invalid values.
Spec integration with mode-aware rule filtering
code/go/internal/validator/spec.go
Add mode modes.Mode to Spec; NewSpec now accepts and validates a mode; rule definitions gain optional modes []modes.Mode selectors and rule iteration skips rules not applicable to the current Spec.mode.
Spec test updates
code/go/internal/validator/spec_test.go
Rename TestNewSpecTestNewLegacySpec; construct specs with modes.Legacy and set Spec.mode in existing spec literals to preserve legacy behavior in tests.
Validator public API and Mode mapping
code/go/pkg/validator/modes.go, code/go/pkg/validator/validator.go
Add public validator.Mode presets (ModeLegacy, ModeSource, ModeBuild) that wrap internal modes and optionally wrap FS to block links; refactor package validator into a Validator type with NewFromPath, NewFromZip, NewFromFS, Option/WithWarningsAsErrors, and (*Validator).Validate() which constructs internal Spec with the selected mode and applies WarningsAsErrors. Remove previous ValidateFromFS helper and make path/zip wrappers construct and invoke a Validator.
Validator test changes and new mode behavior tests
code/go/pkg/validator/validator_test.go, code/go/pkg/validator/limits_test.go
Tests updated to build Validator instances via NewFromFS/constructors, enable warnings-as-errors via options, and assert mode-dependent link handling; add TestLinksBehaviorAcrossModes and TestNewFromFS_TakesFSAsIsRegardlessOfMode; update TestLimitsValidation to use NewFromFS(ModeLegacy, ...).
Changelog entry
spec/changelog.yml
Add enhancement note under 3.7.0-next for “mode-aware constructors and validation APIs”.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I am a rabbit in the code so spry,
Modes bloom like carrots in a row—oh my!
Legacy, Source, Build each hop their lane,
Tests nibble links, specs learn their name. 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: introducing mode-aware constructors and validation APIs to the validator package.
Linked Issues check ✅ Passed The PR successfully implements core coding requirements from issue #549: mode-aware validation constructors, per-rule mode filtering in Spec, build/source/legacy mode constants, and link-file handling across modes.
Out of Scope Changes check ✅ Passed All changes are directly scoped to introducing mode-aware validation APIs; changelog entry documents the feature; no unrelated modifications detected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Documented the addition of mode-aware constructors and validation APIs.
- Updated changelog to reflect enhancements made in the package specification.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/go/internal/validator/spec_test.go (1)

19-40: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Cover the new NewSpec mode branches.

This test only exercises the explicit legacy path. The new empty-mode fallback and invalid-mode error path introduced in NewSpec are still untested.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/go/internal/validator/spec_test.go` around lines 19 - 40, Extend the
TestNewSpec table to include two more cases for NewSpec: one calling
NewSpec(*semver.MustParse(version), modes.Empty) (the zero/empty-mode fallback)
and asserting it behaves like the legacy path (no error and returns *Spec), and
another calling NewSpec(*semver.MustParse(version), modes.Mode(999)) (an
invalid/unknown mode) and asserting it returns an error containing the
invalid-mode message and nil spec; use the same pattern as the existing cases
(require.NoError/IsType or require.Error/Contains/IsNil) and reference NewSpec
and the modes value you pass to locate the logic under test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/go/pkg/validator/api_test.go`:
- Around line 183-192: TestLegacyPreservation_FromZip currently only calls
NewFromZip and doesn't verify legacy-preservation behavior; update the test to
call ValidateFromZip(zipPath) and also construct a legacy validator with
newFromZip(ModeLegacy, zipPath).Validate() (or call newFromZip(ModeLegacy,
zipPath) then .Validate()) and assert that the validation results
(errors/warnings or success) match between ValidateFromZip and the legacy-path
Validate() to ensure the deprecated zip path is preserved; reference
TestLegacyPreservation_FromZip, ValidateFromZip, newFromZip, ModeLegacy, and
Validate when adding these assertions.

In `@code/go/pkg/validator/api.go`:
- Around line 45-61: The constructors NewFromPath and NewFromFS should validate
the Mode parameter immediately and return an error for unknown/invalid Mode
values instead of deferring failure to Validate()/internalvalidator.NewSpec; add
a mode check (e.g., allow only ModeBuild, ModeLegacy, ModeSource) at the top of
NewFromPath and NewFromFS (or factor into a helper like validateMode(mode Mode)
error) and return a descriptive fmt.Errorf if the mode is invalid, then proceed
to call buildValidator only for valid modes.
- Around line 23-38: The Validator API uses abbreviated identifiers; rename the
struct field fsys to fileSystem and replace short parameter names like va in
Option closures (e.g., WithWarningsAsErrors) with full names such as validator
or vValidator; update all Option type funcs (Option func(*Validator)) and any
other abbreviated variables (per your note also around the other Option
implementations referenced) to use full, descriptive names and adjust all
internal references accordingly (e.g., change va.warningsAsErrors to
validator.warningsAsErrors and fsys usage to fileSystem) to comply with the repo
naming guidelines.
- Around line 126-132: The current NewFromFS branch skips wrapping when fsys is
already a *linkedfiles.FS, allowing callers to bypass ModeLegacy/ModeBuild
semantics; change the logic in NewFromFS so that if mode is ModeLegacy or
ModeBuild you always wrap/replace any incoming *linkedfiles.FS with the blocking
variant (use linkedfiles.NewBlockFS) and only preserve an existing
*linkedfiles.FS when mode == ModeSource (or explicitly Source behavior is
intended). Update the conditional around the linkedfiles.FS type check to
inspect mode (ModeSource vs ModeLegacy/ModeBuild) and call
linkedfiles.NewFS(location, fsys) for ModeSource or linkedfiles.NewBlockFS(fsys)
for legacy/build modes so ValidateFromFS tests continue to block .link files as
expected.

---

Outside diff comments:
In `@code/go/internal/validator/spec_test.go`:
- Around line 19-40: Extend the TestNewSpec table to include two more cases for
NewSpec: one calling NewSpec(*semver.MustParse(version), modes.Empty) (the
zero/empty-mode fallback) and asserting it behaves like the legacy path (no
error and returns *Spec), and another calling
NewSpec(*semver.MustParse(version), modes.Mode(999)) (an invalid/unknown mode)
and asserting it returns an error containing the invalid-mode message and nil
spec; use the same pattern as the existing cases (require.NoError/IsType or
require.Error/Contains/IsNil) and reference NewSpec and the modes value you pass
to locate the logic under test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 1731f616-9424-48cd-bb23-ab39ba6340c9

📥 Commits

Reviewing files that changed from the base of the PR and between 5b54815 and 324c4a0.

📒 Files selected for processing (9)
  • .gitignore
  • code/go/internal/validator/modes/modes.go
  • code/go/internal/validator/spec.go
  • code/go/internal/validator/spec_test.go
  • code/go/pkg/validator/api.go
  • code/go/pkg/validator/api_test.go
  • code/go/pkg/validator/mode.go
  • code/go/pkg/validator/validator.go
  • code/go/pkg/validator/validator_test.go

Comment thread code/go/pkg/validator/api_test.go Outdated
Comment thread code/go/pkg/validator/api.go Outdated
Comment thread code/go/pkg/validator/api.go Outdated
Comment thread code/go/pkg/validator/api.go Outdated
teresaromero and others added 2 commits June 2, 2026 09:46
…omZip

- NewFromPath/NewFromFS now reject invalid modes at construction time
- NewFromFS ModeLegacy preserves a pre-wrapped *linkedfiles.FS (matching old
  validateFromFS behaviour); ModeBuild always applies BlockFS unconditionally
- Collapse private newFromZip into NewFromZip; ValidateFromZip delegates to
  NewFromZip directly, consistent with the other deprecated wrappers
- Replace tautological TestLegacyPreservation_* tests with focused tests for
  the new API behaviours: invalid mode rejection, ModeBuild link blocking,
  ModeLegacy/ModeSource linked-FS preservation
- Add good_v3 to TestValidateWarnings to exercise the link-resolution path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NewFromZip owns the zip reader via closer; without calling Validate() the
reader stays open and t.TempDir cleanup fails on Windows with "file is being
used by another process". Call Validate() to close the owned handle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/go/pkg/validator/api_test.go (1)

29-54: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Rename pkgName to packageName per naming guideline.

pkgName (lines 29, 32, 54) is the exact abbreviation called out in the guidelines. Use the full name.

♻️ Proposed rename
-	pkgName := filepath.Base(packagePath)
+	packageName := filepath.Base(packagePath)
 
 	tmpDir := t.TempDir()
-	zipPath := filepath.Join(tmpDir, pkgName+".zip")
+	zipPath := filepath.Join(tmpDir, packageName+".zip")
-		entryName := filepath.ToSlash(filepath.Join(pkgName, rel))
+		entryName := filepath.ToSlash(filepath.Join(packageName, rel))

As per coding guidelines: "Go variable and function names must use full names, not abbreviations (e.g., packageName not pkgName...)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/go/pkg/validator/api_test.go` around lines 29 - 54, Rename the
abbreviated variable pkgName to packageName throughout this test (declaration
and all uses) so it follows naming guidelines; specifically change the value
assignment using filepath.Base(packagePath), update the zipPath construction
that uses pkgName+".zip", and update the entryName computation that joins
pkgName with rel (and any other references such as zip entry prefixing) to use
packageName instead, ensuring you update variable references in os.Create,
filepath.Join, and filepath.ToSlash calls within api_test.go.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@code/go/pkg/validator/api_test.go`:
- Around line 29-54: Rename the abbreviated variable pkgName to packageName
throughout this test (declaration and all uses) so it follows naming guidelines;
specifically change the value assignment using filepath.Base(packagePath),
update the zipPath construction that uses pkgName+".zip", and update the
entryName computation that joins pkgName with rel (and any other references such
as zip entry prefixing) to use packageName instead, ensuring you update variable
references in os.Create, filepath.Join, and filepath.ToSlash calls within
api_test.go.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 55ee2b22-e089-47e4-85b9-3209f7d29b11

📥 Commits

Reviewing files that changed from the base of the PR and between 324c4a0 and 491658c.

📒 Files selected for processing (5)
  • code/go/pkg/validator/api.go
  • code/go/pkg/validator/api_test.go
  • code/go/pkg/validator/validator.go
  • code/go/pkg/validator/validator_test.go
  • spec/changelog.yml

@teresaromero

Copy link
Copy Markdown
Contributor Author

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)

code/go/pkg/validator/api_test.go (1)> 29-54: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Rename pkgName to packageName per naming guideline.
pkgName (lines 29, 32, 54) is the exact abbreviation called out in the guidelines. Use the full name.

♻️ Proposed rename

  • pkgName := filepath.Base(packagePath)
  • packageName := filepath.Base(packagePath)

    tmpDir := t.TempDir()

  • zipPath := filepath.Join(tmpDir, pkgName+".zip")
  • zipPath := filepath.Join(tmpDir, packageName+".zip")
  • entryName := filepath.ToSlash(filepath.Join(pkgName, rel))
    
  • entryName := filepath.ToSlash(filepath.Join(packageName, rel))
    

As per coding guidelines: "Go variable and function names must use full names, not abbreviations (e.g., packageName not pkgName...)".

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/go/pkg/validator/api_test.go` around lines 29 - 54, Rename the
abbreviated variable pkgName to packageName throughout this test (declaration
and all uses) so it follows naming guidelines; specifically change the value
assignment using filepath.Base(packagePath), update the zipPath construction
that uses pkgName+".zip", and update the entryName computation that joins
pkgName with rel (and any other references such as zip entry prefixing) to use
packageName instead, ensuring you update variable references in os.Create,
filepath.Join, and filepath.ToSlash calls within api_test.go.

🤖 Prompt for all review comments with AI agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@code/go/pkg/validator/api_test.go`:
- Around line 29-54: Rename the abbreviated variable pkgName to packageName
throughout this test (declaration and all uses) so it follows naming guidelines;
specifically change the value assignment using filepath.Base(packagePath),
update the zipPath construction that uses pkgName+".zip", and update the
entryName computation that joins pkgName with rel (and any other references such
as zip entry prefixing) to use packageName instead, ensuring you update variable
references in os.Create, filepath.Join, and filepath.ToSlash calls within
api_test.go.

ℹ️ Review info

i will keep it as is thanks!

@jsoriano jsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall looks good. Questioning some small design decisions and doing some nitpicking.

Comment thread code/go/internal/validator/modes.go
Comment thread code/go/internal/validator/spec.go Outdated
Comment thread code/go/pkg/validator/api.go Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit. "api" is redundant here, any public method is going to be part of the package API 🙂

This could belong to validator.go, as it actually defines the Validator object.

Comment thread code/go/pkg/validator/api.go Outdated
//
// For ModeLegacy and ModeSource the filesystem honours linked (.link) files;
// for ModeBuild linked files are blocked (matching a built package artifact).
func NewFromPath(mode Mode, packageRootPath string, opts ...Option) (*Validator, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit. Configuration structs are usually easier to discover than functional options, and use to be simpler.

Suggested change
func NewFromPath(mode Mode, packageRootPath string, opts ...Option) (*Validator, error) {
type Config struct {
WarningsAsErrors bool
}
func NewFromPath(mode Mode, packageRootPath string, config Config) (*Validator, error) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This config is used for the warnings, which is read from the env, so i removed the options/config and read it directly from the env. Changed the test case too to use t.Env instead of a local variable.

v := &Validator{
		mode:             mode,
		location:         location,
		fsys:             fsys,
		warningsAsErrors: common.IsDefinedWarningsAsErrors(),
		closer:           closer,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Part of the original design in #549 was actually to introduce the "Option C" API shape, a new "Validator" object that can be somehow customized with different options.

With current code we are back to something like "Option A": methods with the mode as parameter. Only that we have one method per mode instead of having the mode as a parameter.

I still think it would be valuable to introduce this "Validator" object. Having the object we can add configuration options later without needing to add more functions to the package, or parameters to these functions.

For this initial version it could have warningsAsErros as single configuration, and it could have two validation methods (build and source) if we prefer this approach instead of modes as a parameter.

Comment thread code/go/pkg/validator/api.go Outdated
Comment thread .gitignore Outdated
Comment thread code/go/pkg/validator/api.go Outdated
switch mode {
case ModeSource:
if !isLinkedFS {
fsys = linkedfiles.NewFS(location, fsys)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

On what cases we receive a linked FS here?

Nit. Maybe NewFS and NewBlockFS should decide what to do when called with a linkedfiles FS of each kind, so their callers don't need to care.

Comment thread code/go/pkg/validator/api.go Outdated
Comment on lines +144 to +145
// Always block, even a pre-wrapped *linkedfiles.FS.
fsys = linkedfiles.NewBlockFS(fsys)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Umm, not sure if we should do this at this level. Could we instead reject .lnk files in built packages as we will reject _dev files?

Or we need to do this because the spec always receives the link files resolved?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i am not sure i understand this, or the lines have moved around. here i am validating the fsys, not any linked files. The fs is checked dependeing on the mode "selected" if it allows or not .link files. For build validation there link should be resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When validating a built package there should not be any .link files.

I was wondering how we are ensuring that, and if the different FSs used was because of that.

Comment thread code/go/pkg/validator/api.go Outdated
Comment on lines +147 to +148
// Preserve a pre-wrapped *linkedfiles.FS; block everything else.
// Matches the old validateFromFS behaviour.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure if this would be needed now. I think this was used before because NewFromPath generated its own linkedfiles.FS. But not sure if this is the case now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i need to review this whole refactor, as i am thinking it will be better to have direct constructors for modes, instead of using the mode as a switch...

- Removed the Option parameter from NewFromPath, NewFromZip, and NewFromFS constructors, simplifying their signatures.
- Updated internal buildValidator function to reflect the changes, eliminating the need for options.
- Adjusted tests to align with the new constructor signatures, ensuring proper validation behavior without options.

@teresaromero teresaromero left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

thanks for the feedback

Comment thread code/go/internal/validator/modes.go
Comment thread code/go/internal/validator/spec.go Outdated
Comment thread code/go/pkg/validator/api.go Outdated
Comment on lines +144 to +145
// Always block, even a pre-wrapped *linkedfiles.FS.
fsys = linkedfiles.NewBlockFS(fsys)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i am not sure i understand this, or the lines have moved around. here i am validating the fsys, not any linked files. The fs is checked dependeing on the mode "selected" if it allows or not .link files. For build validation there link should be resolved.

Comment thread code/go/pkg/validator/api.go Outdated
Comment on lines +147 to +148
// Preserve a pre-wrapped *linkedfiles.FS; block everything else.
// Matches the old validateFromFS behaviour.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i need to review this whole refactor, as i am thinking it will be better to have direct constructors for modes, instead of using the mode as a switch...

teresaromero and others added 5 commits June 3, 2026 11:22
- Enhanced godoc comments for all public validator functions (ValidateFromPath,
  ValidateFromBuildPath, ValidateFromSourcePath, ValidateFromZip) with clearer
  descriptions of their purpose, which specification they use, and linked file handling
- Fixed typo: "appropiate" → "appropriate" in validateFromFS comment
- Updated modes documentation to accurately describe the three validation modes
  (Legacy, Source, Build) instead of referencing non-existent public re-exports
- Added deprecation guidance directing users to mode-specific functions

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@teresaromero teresaromero requested a review from jsoriano June 3, 2026 10:02
@teresaromero

Copy link
Copy Markdown
Contributor Author

@jsoriano i've changed the aproach, having in mind the mode belongs to the spec as a compliment of the definition. this is ready for re-review. thnks

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/go/pkg/validator/validator.go`:
- Around line 98-103: validateFromFS currently preserves an incoming
*linkedfiles.FS which lets build-mode callers pass through link-resolving FSs;
change validateFromFS to make link-blocking depend on the validation mode (e.g.,
add a parameter validationMode or a boolean blockLinks) and ensure that when
validationMode == ModeBuild you unconditionally replace/wrap fsys with
linkedfiles.NewBlockFS(fsys) (do not check the incoming type), while for other
modes keep the existing behavior of only wrapping when fsys is not already a
*linkedfiles.FS; update callers (like NewFromFS) to pass the mode flag
accordingly.
- Around line 39-43: The docstrings for ValidateFromBuildPath and the related
build-mode entrypoint should be updated to match the current behavior:
Spec.rules() is now mode-aware, but since no mode-gated rules are registered
yet, these paths only guarantee .link blocking. Adjust the comments on
ValidateFromBuildPath (and the other build-mode docstring noted in the review)
to remove the claim that source-only artifacts are rejected, and describe only
the validation behavior actually implemented by Spec.rules().
- Around line 70-74: The defer currently ignores the zip.ReadCloser close error;
change the defer r.Close() to a deferred closure that captures and checks the
close error (from r.Close()) and if non-nil merges/wraps it into the function's
returned error (e.g. when err is nil set/return the close error or wrap both
when err is non-nil). Locate the zip.OpenReader call that assigns r and replace
the plain defer with a deferred func that observes cerr := r.Close() and returns
or wraps cerr appropriately (or use a named return error variable so the
deferred func can set it).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: aec8c75a-0b94-440c-8c8a-34548b6e6308

📥 Commits

Reviewing files that changed from the base of the PR and between fcaf8c2 and 4233331.

📒 Files selected for processing (7)
  • code/go/internal/validator/modes/modes.go
  • code/go/internal/validator/modes/modes_test.go
  • code/go/internal/validator/spec.go
  • code/go/internal/validator/spec_test.go
  • code/go/pkg/validator/limits_test.go
  • code/go/pkg/validator/validator.go
  • code/go/pkg/validator/validator_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/go/pkg/validator/validator.go`:
- Around line 98-103: validateFromFS currently preserves an incoming
*linkedfiles.FS which lets build-mode callers pass through link-resolving FSs;
change validateFromFS to make link-blocking depend on the validation mode (e.g.,
add a parameter validationMode or a boolean blockLinks) and ensure that when
validationMode == ModeBuild you unconditionally replace/wrap fsys with
linkedfiles.NewBlockFS(fsys) (do not check the incoming type), while for other
modes keep the existing behavior of only wrapping when fsys is not already a
*linkedfiles.FS; update callers (like NewFromFS) to pass the mode flag
accordingly.
- Around line 39-43: The docstrings for ValidateFromBuildPath and the related
build-mode entrypoint should be updated to match the current behavior:
Spec.rules() is now mode-aware, but since no mode-gated rules are registered
yet, these paths only guarantee .link blocking. Adjust the comments on
ValidateFromBuildPath (and the other build-mode docstring noted in the review)
to remove the claim that source-only artifacts are rejected, and describe only
the validation behavior actually implemented by Spec.rules().
- Around line 70-74: The defer currently ignores the zip.ReadCloser close error;
change the defer r.Close() to a deferred closure that captures and checks the
close error (from r.Close()) and if non-nil merges/wraps it into the function's
returned error (e.g. when err is nil set/return the close error or wrap both
when err is non-nil). Locate the zip.OpenReader call that assigns r and replace
the plain defer with a deferred func that observes cerr := r.Close() and returns
or wraps cerr appropriately (or use a named return error variable so the
deferred func can set it).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: aec8c75a-0b94-440c-8c8a-34548b6e6308

📥 Commits

Reviewing files that changed from the base of the PR and between fcaf8c2 and 4233331.

📒 Files selected for processing (7)
  • code/go/internal/validator/modes/modes.go
  • code/go/internal/validator/modes/modes_test.go
  • code/go/internal/validator/spec.go
  • code/go/internal/validator/spec_test.go
  • code/go/pkg/validator/limits_test.go
  • code/go/pkg/validator/validator.go
  • code/go/pkg/validator/validator_test.go
🛑 Comments failed to post (3)
code/go/pkg/validator/validator.go (3)

39-43: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align the build-mode docstrings with the behavior shipped in this PR.

Spec.rules() is mode-aware now, but there are no mode-gated rules registered yet, so these entrypoints currently guarantee .link blocking only. Saying build validation rejects source-only artifacts overpromises the API contract.

Suggested wording
-// Linked files (.link) are blocked; source-only artifacts are rejected.
+// Linked files (.link) are blocked.
+// Additional build-mode-only validation rules can be layered on as mode-gated rules are introduced.

Also applies to: 66-68

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/go/pkg/validator/validator.go` around lines 39 - 43, The docstrings for
ValidateFromBuildPath and the related build-mode entrypoint should be updated to
match the current behavior: Spec.rules() is now mode-aware, but since no
mode-gated rules are registered yet, these paths only guarantee .link blocking.
Adjust the comments on ValidateFromBuildPath (and the other build-mode docstring
noted in the review) to remove the claim that source-only artifacts are
rejected, and describe only the validation behavior actually implemented by
Spec.rules().

70-74: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle the zip.ReadCloser close error instead of dropping it.

Line 74 is already flagged by errcheck, and a close failure is currently ignored even though this function returns an error.

One simple fix
 func ValidateFromZip(packagePath string) error {
 	r, err := zip.OpenReader(packagePath)
 	if err != nil {
 		return fmt.Errorf("failed to open zip file (%s): %w", packagePath, err)
 	}
-	defer r.Close()
 
 	dirs, err := fs.ReadDir(r, ".")
 	if err != nil {
+		_ = r.Close()
 		return fmt.Errorf("failed to read root directory in zip file (%s): %w", packagePath, err)
 	}
 	if len(dirs) != 1 {
+		_ = r.Close()
 		return fmt.Errorf("a single directory is expected in zip file, %d found", len(dirs))
 	}
 
 	subDir, err := fs.Sub(r, dirs[0].Name())
 	if err != nil {
+		_ = r.Close()
 		return err
 	}
 
 	buildSpec := func(version semver.Version) (*validator.Spec, error) {
 		return validator.NewBuildSpec(version)
 	}
 
-	return validateFromFS(packagePath, subDir, buildSpec)
+	err = validateFromFS(packagePath, subDir, buildSpec)
+	closeErr := r.Close()
+	if err != nil {
+		return err
+	}
+	return closeErr
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func ValidateFromZip(packagePath string) error {
	r, err := zip.OpenReader(packagePath)
	if err != nil {
		return fmt.Errorf("failed to open zip file (%s): %w", packagePath, err)
	}

	dirs, err := fs.ReadDir(r, ".")
	if err != nil {
		_ = r.Close()
		return fmt.Errorf("failed to read root directory in zip file (%s): %w", packagePath, err)
	}
	if len(dirs) != 1 {
		_ = r.Close()
		return fmt.Errorf("a single directory is expected in zip file, %d found", len(dirs))
	}

	subDir, err := fs.Sub(r, dirs[0].Name())
	if err != nil {
		_ = r.Close()
		return err
	}

	buildSpec := func(version semver.Version) (*validator.Spec, error) {
		return validator.NewBuildSpec(version)
	}

	err = validateFromFS(packagePath, subDir, buildSpec)
	closeErr := r.Close()
	if err != nil {
		return err
	}
	return closeErr
}
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 74-74: Error return value of r.Close is not checked

(errcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/go/pkg/validator/validator.go` around lines 70 - 74, The defer currently
ignores the zip.ReadCloser close error; change the defer r.Close() to a deferred
closure that captures and checks the close error (from r.Close()) and if non-nil
merges/wraps it into the function's returned error (e.g. when err is nil
set/return the close error or wrap both when err is non-nil). Locate the
zip.OpenReader call that assigns r and replace the plain defer with a deferred
func that observes cerr := r.Close() and returns or wraps cerr appropriately (or
use a named return error variable so the deferred func can set it).

98-103: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make link blocking depend on validation mode, not the incoming FS type.

This helper preserves any pre-wrapped *linkedfiles.FS. A build-mode caller that passes one will resolve .link files instead of rejecting them, which breaks the new build-mode contract and makes validateFromFS unsafe to reuse for build validation.

Based on learnings: In code/go/pkg/validator/api.go (NewFromFS), ModeBuild always replaces with linkedfiles.NewBlockFS, even if a *linkedfiles.FS is passed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/go/pkg/validator/validator.go` around lines 98 - 103, validateFromFS
currently preserves an incoming *linkedfiles.FS which lets build-mode callers
pass through link-resolving FSs; change validateFromFS to make link-blocking
depend on the validation mode (e.g., add a parameter validationMode or a boolean
blockLinks) and ensure that when validationMode == ModeBuild you unconditionally
replace/wrap fsys with linkedfiles.NewBlockFS(fsys) (do not check the incoming
type), while for other modes keep the existing behavior of only wrapping when
fsys is not already a *linkedfiles.FS; update callers (like NewFromFS) to pass
the mode flag accordingly.

@jsoriano jsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, it looks simpler now.

Some notes:

  • Using specific methods for the validation modes LGTM, at the end I don't think we are going to have more modes beyond Source and Build.
  • With the last changes we are losing the "Option C" API shape, that would be a "Validator" object that could have different settings, so we don't need to add additional methods or parameters to these methods if we need more settings in the future.
  • Please check that links are not allowed in built packages. They should have been resolved when building the package.

Comment thread code/go/pkg/validator/limits_test.go Outdated
Comment thread code/go/pkg/validator/validator.go Outdated
Comment thread code/go/pkg/validator/validator.go Outdated
Comment thread code/go/pkg/validator/validator.go Outdated
Comment thread code/go/pkg/validator/validator.go Outdated
Comment thread code/go/internal/validator/spec.go Outdated
Comment thread code/go/pkg/validator/api.go Outdated
//
// For ModeLegacy and ModeSource the filesystem honours linked (.link) files;
// for ModeBuild linked files are blocked (matching a built package artifact).
func NewFromPath(mode Mode, packageRootPath string, opts ...Option) (*Validator, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Part of the original design in #549 was actually to introduce the "Option C" API shape, a new "Validator" object that can be somehow customized with different options.

With current code we are back to something like "Option A": methods with the mode as parameter. Only that we have one method per mode instead of having the mode as a parameter.

I still think it would be valuable to introduce this "Validator" object. Having the object we can add configuration options later without needing to add more functions to the package, or parameters to these functions.

For this initial version it could have warningsAsErros as single configuration, and it could have two validation methods (build and source) if we prefer this approach instead of modes as a parameter.

Comment thread code/go/pkg/validator/api.go Outdated
Comment on lines +144 to +145
// Always block, even a pre-wrapped *linkedfiles.FS.
fsys = linkedfiles.NewBlockFS(fsys)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When validating a built package there should not be any .link files.

I was wondering how we are ensuring that, and if the different FSs used was because of that.

teresaromero and others added 3 commits June 4, 2026 09:50
…cking

- Mode becomes a first-class public type (not a string alias) carrying both
  the internal rule set and a wrapFS factory for path-based construction;
  external callers use ModeLegacy, ModeSource, ModeBuild only

- NewFromPath delegates FS setup entirely to mode.wrapFS, removing the
  switch-on-mode logic from constructors

- NewFromFS is now a "bring your own FS" constructor: mode selects validation
  rules only, no link-file wrapping is applied; callers are responsible for FS
  semantics (fixes the silent bypass where a *linkedfiles.FS passed to build
  mode would resolve instead of block links)

- NewFromZip always runs ModeBuild with an explicit BlockFS; its zip reader
  is owned by the Validator and closed by Validate() with errors.Join (fixes
  the dropped r.Close() error previously flagged by errcheck)

- Expose internal newSpec as NewSpec(version, mode) so Validator.Validate()
  can pass the stored mode.internal directly without a switch

- ValidateFromPath and ValidateFromZip remain as zero-param thin wrappers;
  ValidateFromBuildPath and ValidateFromSourcePath are removed (replaced by
  NewFromPath(ModeBuild/ModeSource, ...))

- WithWarningsAsErrors Option overrides PACKAGE_SPEC_WARNINGS_AS_ERRORS env var

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Give each Mode constant its own godoc line so go doc can attach them
- Describe Mode in terms of what it controls, not just what it is
- Remove ModeLegacy anchor to deprecated ValidateFromPath function
- Add rule-gating dimension to ModeSource and ModeBuild docs
- Clarify WithWarningsAsErrors overrides the env var in both directions
- Note error conditions on NewFromPath, NewFromFS, and NewSpec
- Warn NewFromFS callers to wrap with NewBlockFS when using ModeBuild
- Move single-call restriction note from Validate to NewFromZip only
- Replace undefined term "build specification" with ModeBuild in ValidateFromZip
- Expand ValidatePackage doc to mention both syntactic and semantic rules

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/go/internal/validator/spec_test.go (1)

19-40: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add a direct invalid-mode case for NewSpec.

This test now exercises the new signature, but it still only covers version failures. Adding a NewSpec(validVersion, modes.Mode("invalid")) case would lock down the constructor’s own mode.Valid() branch and catch regressions even if caller-side validation changes later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/go/internal/validator/spec_test.go` around lines 19 - 40, Add a direct
"invalid mode" subcase to TestNewLegacySpec: call NewSpec with a valid semver
(e.g., semver.MustParse("1.0.0")) and an invalid mode value created as
modes.Mode("invalid"), then assert that it returns an error (require.Error) and
that spec is nil (require.Nil) and the error message contains the expected
invalid-mode text coming from NewSpec/mode.Valid() branch; this locks the
constructor's mode validation (NewSpec and modes.Mode) against regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/go/pkg/validator/modes.go`:
- Line 1: Add the required Elastic License copyright header to the top of the
new Go file that declares package validator (modes.go); insert the canonical
Elastic License header immediately above the existing "package validator" line
so the file begins with the mandated header before any package/imports or code.

---

Outside diff comments:
In `@code/go/internal/validator/spec_test.go`:
- Around line 19-40: Add a direct "invalid mode" subcase to TestNewLegacySpec:
call NewSpec with a valid semver (e.g., semver.MustParse("1.0.0")) and an
invalid mode value created as modes.Mode("invalid"), then assert that it returns
an error (require.Error) and that spec is nil (require.Nil) and the error
message contains the expected invalid-mode text coming from NewSpec/mode.Valid()
branch; this locks the constructor's mode validation (NewSpec and modes.Mode)
against regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 1286ec04-b7c3-4a54-9100-d0fb08be6e9c

📥 Commits

Reviewing files that changed from the base of the PR and between 4233331 and b0049ff.

📒 Files selected for processing (7)
  • code/go/internal/validator/modes/modes.go
  • code/go/internal/validator/spec.go
  • code/go/internal/validator/spec_test.go
  • code/go/pkg/validator/limits_test.go
  • code/go/pkg/validator/modes.go
  • code/go/pkg/validator/validator.go
  • code/go/pkg/validator/validator_test.go

Comment thread code/go/pkg/validator/modes.go Outdated
@teresaromero teresaromero requested a review from jsoriano June 4, 2026 10:28
@teresaromero

Copy link
Copy Markdown
Contributor Author
  • With the last changes we are losing the "Option C" API shape, that would be a "Validator" object that could have different settings, so we don't need to add additional methods or parameters to these methods if we need more settings in the future.
  • Please check that links are not allowed in built packages. They should have been resolved when building the package.

I've reintroduced the Validator struct. in order to have the modes as public there is the Mode type which owns the fs for each mode. This way fs are determined. I've kept NewFromFS as a constructor with the fs open for the user to use under its responsability

Comment thread code/go/internal/validator/modes.go
Comment thread code/go/pkg/validator/validator.go Outdated
Comment on lines +121 to +126
if v.closer != nil {
defer func() {
if cerr := v.closer.Close(); cerr != nil {
err = errors.Join(err, cerr)
}
}()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Validate should not close the underlying filesystem. It would be an unexpected side effect.

If Validator creates its own filesystems or resources that need to be cleaned up, it should expose its own Close method.

Comment thread code/go/pkg/validator/validator.go Outdated
return nil, fmt.Errorf("invalid validation mode %q", mode.internal)
}
defer r.Close()
fsys := mode.wrapFS(packageRootPath, os.DirFS(packageRootPath))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit. If this wrapFS is only used in this case, it would simplify things to handle it only here.

Suggested change
fsys := mode.wrapFS(packageRootPath, os.DirFS(packageRootPath))
fsys := os.DirFS(packageRootPath)
if mode == ModeBuild {
fsys = linkedfiles.NewBlockFS(fsys)
} else {
fsys = linkedfiles.NewFS(packageRootPath, fsys)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i initially had this if/else but i thought to enforce the fs is owned by the mode, so wrapFs represents the fs asigned by the mode... so this constructor just uses whatever the mode has and is not responsible of creating it, if that makes sense? happy to change it if this is not correct

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Functionality seems to be correct, the only issue I see is that it is adding complexity with an abstraction that is only used in a place.

In general all this handling of links gives me a bit of bad feeling, but I don't have better ideas.

In any case all these are internal details, so let it with the approach you prefer.

@jsoriano jsoriano Jun 5, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you keep modes as objects with wrapFS methods I would suggest to convert Mode in an interface, and make wrapFS a method of the implementing objects.

type Mode interface {
    mode() modes.Mode
    wrapFS(location string, fsys fs.FS) fs.FS
}

type SourceMode struct {}
func (SourceMode) mode() modes.Mode { return mode.Source }
func (SourceMode) wrapFS(location string, fsys fs.FS) fs.FS {
    return linkedfiles.NewFS(location, fsys)
}

...

This looks more aligned with Go conventions.


// ValidateFromFS validates a package against the appropiate specification and returns any errors.
// Package files are obtained through the given filesystem.
func ValidateFromFS(location string, fsys fs.FS) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This method is removed, right? This would be a breaking change in the public API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ValidateFromFS although is a public method is just being used at tests files. I've reviewed elastic-package and dont see this function used explicitly so, its a breaking change because is no longer available but is not being used 🤔 I will revert and keep it, although i think we should deprecate it in favour of NewFromFS

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should deprecate all ValidateFrom methods at the package level, and keep in the long term only the Validator object.
In the meantime let's keep all of them, just to follow good practices, just in case.

Comment thread code/go/pkg/validator/validator.go Outdated
if _, ok := fsys.(*linkedfiles.FS); !ok {
fsys = linkedfiles.NewBlockFS(fsys)
// Validate runs package validation and returns any errors encountered.
func (v *Validator) Validate() (err error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Going back to the API design question. Sorry for the back and forth.

With current design we have the object, but given that it already receives everything in the constructor, it is just like a function with delayed execution.

These two shapes are really not very different, and the first one has more complexity with little value:

v, err := validator.NewFromZip(path, options...)
err = v.Validate()

And:

err := validator.ValidateFromZip(path, options...)

What would give more value to the API shape, and what I'd say the "Option C" was, is to have a stateless validator that clearly separates validation configuration from the validation of one, or more packages.

Something like this:

// Configuration-time
v, err := validator.New(validator.ModeSource,
  validator.WithWarningsAsErrors(true),
  validator.WithFilter(filterConfig),  // future
  validator.WithLogger(log),           // future
)


// Verb-time, repeatable, no shared mutable state
err = v.ValidateFromPath("/pkg1")
err = v.ValidateFromPath("/pkg2")
err = v.ValidateFromZip("/pkg.zip")
err = v.ValidateFromFS("loc", fsys)

teresaromero and others added 4 commits June 5, 2026 09:50
- Introduced NewValidator function to replace NewFromPath and NewFromFS, simplifying the creation of Validator instances.
- Updated validation methods to use ValidateFromPath and ValidateFromFS, enhancing clarity and consistency.
- Removed deprecated functions and unnecessary wrapping logic, ensuring better adherence to mode-specific validation rules.
- Added tests for link file behavior across validation modes to ensure expected functionality.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Deleted the modes.go file to streamline the codebase.
- Moved mode type definitions and constants directly into validator.go for better organization and accessibility.
- Introduced multiple test cases for ValidateFromFS to handle various file system scenarios, including linked and block file systems.
- Added tests for ValidateFromZip to enforce mode restrictions and validate package integrity.
- Enhanced documentation comments for validation modes and methods to clarify their usage and constraints.
- Implemented helper functions for creating zip files for testing purposes.
@teresaromero teresaromero requested a review from jsoriano June 5, 2026 10:34

@jsoriano jsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good overall, added some comments about some small details.

Comment thread code/go/pkg/validator/validator.go Outdated
return errors.New("linked files are not supported in ModeBuild")
} else if _, ok := fsys.(*linkedfiles.BlockFS); ok && v.mode == ModeSource {
return errors.New("block linked files are not supported in ModeSource")
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I still don't like the handling of linked file systems, but this is internal and we can revisit later, I don't have much better ideas now.

Comment thread code/go/pkg/validator/validator.go Outdated
Comment thread code/go/pkg/validator/validator.go Outdated
}
spec.WarningsAsErrors = warningsAsErrors
spec.WarningsAsErrors = v.warningsAsErrors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe we should append a technical preview warning when Source or Build modes are used, at least till we complete the validators.

@teresaromero teresaromero Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've added just using the log pkg as i havent seen any logger implemented on the package d895e49

i followed the same pattern as in

log.Printf("Warning: %s", err.Error())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah, we don't have an abstraction for warnings, maybe we should add it.

We could leverage warningsAsErrors, something like this:

errs := spec.ValidatePackage(*pkg)
if v.mode != LegacyMode {
    err := specerrors.NewStructuredErrorf("validation mode '%s' is in technical preview", v.mode)
    if v.warningsAsErrors() {
        errs = append(errs, err)
    } else {
        log.Printf("Warning: %s", err.Message())
    }
}
if len(errs) > 0 {
    return errs
}
return nil

- Renamed mode constants from ModeLegacy, ModeSource, and ModeBuild to LegacyMode, SourceMode, and BuildMode for improved readability.
- Updated all references in tests and validation logic to reflect the new naming convention.
- Enhanced test cases to ensure proper validation behavior across different modes.
- Renamed NewValidator function to New for improved clarity and consistency.
- Updated all references in tests and validation logic to use the new instantiation method.
- Ensured that the new method maintains the same functionality while simplifying the API.
- Adjusted test cases to reflect the changes in the validator creation process.
- Introduced a log statement to warn users when the validation mode is in technical preview.
- Ensured that the logging occurs only when the mode is not set to LegacyMode, enhancing user awareness of the current validation state.
@elasticmachine

Copy link
Copy Markdown

💚 Build Succeeded

History

}
spec.WarningsAsErrors = warningsAsErrors
spec.WarningsAsErrors = v.warningsAsErrors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah, we don't have an abstraction for warnings, maybe we should add it.

We could leverage warningsAsErrors, something like this:

errs := spec.ValidatePackage(*pkg)
if v.mode != LegacyMode {
    err := specerrors.NewStructuredErrorf("validation mode '%s' is in technical preview", v.mode)
    if v.warningsAsErrors() {
        errs = append(errs, err)
    } else {
        log.Printf("Warning: %s", err.Message())
    }
}
if len(errs) > 0 {
    return errs
}
return nil

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.

[Change Proposal] Add different validation modes for source and built packages

3 participants