Skip to content

Introduce structured FBC types: MajorMinor and Date#29

Open
joelanford wants to merge 1 commit into
mainfrom
jl/structured-fbc-types
Open

Introduce structured FBC types: MajorMinor and Date#29
joelanford wants to merge 1 commit into
mainfrom
jl/structured-fbc-types

Conversation

@joelanford

Copy link
Copy Markdown
Contributor

Summary

  • Replace plain string fields in FBC output types with structured MajorMinor and Date types that enforce format invariants at construction time
  • Version.Name and Platform.Versions use MajorMinor (regex-validated, no leading zeros); Phase.StartDate/EndDate use *Date (nil = no date)
  • Translation functions now return errors (collected via errors.Join) instead of silently passing through malformed data; empty and "N/A" timestamps translate to nil
  • This enforces the FBC lifecycle contract — the schema that downstream consumers depend on — which is separate from the PLCC validation policy that governs data quality in the upstream source. PLCC validators check whether product lifecycle data meets content policy (tier requirements, date contiguity, release cadence, etc.); the FBC type layer ensures the output schema is well-formed by construction regardless of which PLCC validators are enabled.
  • Reference test data regenerated: 146 → 34 packages (pipeline test bypasses PLCC validators, so the type layer now filters packages with formula timestamps, non-MAJOR.MINOR versions, etc.)

Test plan

  • make test passes
  • All type parsing tests (valid/invalid inputs, JSON round-trip)
  • Pipeline tests verify bad versions/timestamps are rejected
  • Writer tests confirm JSON/YAML output format unchanged
  • cmd tests pass with original main_test.go unchanged

Generated with Claude Code

@joelanford joelanford requested a review from a team as a code owner July 1, 2026 20:05
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 1, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:08 PM UTC · Completed 8:23 PM UTC
Commit: feb9bda · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file. No linked issue provides authorization for this change. Human approval is required for all protected-path changes regardless of context.
    Remediation: Link to an issue that authorizes modifications to AGENTS.md, or obtain explicit maintainer approval for the protected-path change.

Medium

  • [architecture-shift-undocumented] pkg/fbc/doc.go:20 — The PR shifts the translation philosophy from "lenient parsing" to "strict translation." While doc.go and AGENTS.md are updated, no formal design document captures the rationale, alternatives considered, or implications for the two-layer validation architecture.
    Remediation: Consider documenting the design rationale more formally if the project adopts ADRs.

  • [breaking-api-change] pkg/fbc/fbc.go:42 — Exported type changes (Version.Name: string→MajorMinor, Phase.StartDate/EndDate: string→*Date, Platform.Versions: []string→[]MajorMinor) are Go-level breaking changes. The module has no git tags or releases (pre-1.0), limiting external impact, but downstream Go importers would break at compile time.
    Remediation: Acknowledge this as a pre-1.0 breaking change in the PR description.

  • [breaking-json-output] pkg/fbc/fbc.go:137 — Phase.StartDate and Phase.EndDate now use omitempty JSON tags. Nil dates are omitted from JSON output rather than appearing as empty strings. In practice, FilterIncompletePhases strips nil-date phases by default, so this mainly affects non-default filter pipelines.
    Remediation: Remove omitempty from Phase date JSON tags to maintain backward compatibility, or document this as a known change.

  • [breaking-behavior] pkg/fbc/fbc.go:156 — Translate() now rejects products with invalid version names (non-MAJOR.MINOR) or unparseable timestamps, whereas previously these passed through with degraded data. This is intentional per the PR description and redundant with existing PLCC validators in the normal pipeline.
    Remediation: Document this behavior change and its interaction with existing PLCC validators.

  • [stale-doc] docs/FBC_SCHEMA.md:45 — Phase fields startDate/endDate are documented as Required=yes but are now optional (omitempty). The schema overview does not reflect that these fields can be absent.
    Remediation: Update docs/FBC_SCHEMA.md: change Phase date fields from Required=yes to Required=no.

Low

  • [missing-authorization] — Non-trivial structural change with no linked issue. The PR has a clear description and "enhancement" label, but formal issue-based authorization is missing.
  • [scope-creep] Makefile:13 — Unrelated addition of -count 1 (disables test caching). Plausibly related to test data regeneration but not documented.
  • [testdata-regeneration-unexplained] pkg/fbc/testdata/reference-fbc.yaml — Reference test data reduced from 146→34 packages. The PR description acknowledges this but doesn't break down rejection reasons.
  • [error-handling] pkg/fbc/fbc.go:159 — Nested errors.Join in newPackage produces multi-line entries in ValidationResult.Reasons, which may render poorly in structured JSON logs.
  • [panic-usage] pkg/fbc/types.go:63 — ParseMajorMinor includes a panic for an unreachable code path. The codebase has no other panic calls in production code.
  • [naming-inconsistency] pkg/fbc/types.go:51 — MajorMinor uses uint64 for Major/Minor fields; this is the default return type of strconv.ParseUint but oversized for version numbers.
Previous run

Review

Findings

High

  • [breaking-api] pkg/fbc/fbc.go — Exported struct field types changed: Version.Name (stringMajorMinor), Phase.StartDate/EndDate (string*Date), Platform.Versions ([]string[]MajorMinor). Any external Go code importing the fbc package will break at compile time. The JSON wire format is preserved (custom marshalers produce the same strings), but the Go API surface is broken. For a v0 module this is permitted by Go module semantics, but the change should be documented as breaking.
    Remediation: Document as a breaking change in the PR description or release notes. If external consumers exist, consider a deprecation path or accessor methods.

  • [stale-doc] AGENTS.md — Two factually incorrect statements after this PR: (1) line 133 states "N/A or empty timestamps translate to empty strings" — they now translate to nil. (2) Line 142 states "newPackage() silently converts unparseable timestamps to empty strings" — newPackage now returns errors for invalid data.
    Remediation: Update both references to reflect nil semantics and error-returning behavior.

  • [stale-doc] pkg/fbc/doc.go — Package documentation states "newPackage translates a PLCC product without failing on bad data (unparseable timestamps become empty strings)". This is now incorrect — newPackage returns errors for invalid data.
    Remediation: Update doc.go to describe the new strict translation behavior.

Medium

  • [missing-authorization] — Non-trivial architectural change (new type system, 2 new files, error handling model change, test data regeneration) without a linked issue. The PR body provides good context, but a linked issue aids traceability for foundational changes.
    See also: [architectural-coherence] finding below.

  • [architectural-coherence] pkg/fbc/fbc.go — Adding error returns to translation functions changes the documented design pattern. AGENTS.md, CLAUDE.md, and doc.go all describe a "lenient parsing" model where the FBC layer silently converts bad data and the PLCC layer handles validation. This PR intentionally shifts to strict validation at the FBC type layer, which is a reasonable design evolution, but the documentation must be updated to reflect it.
    See also: [stale-doc] findings for AGENTS.md and doc.go.

  • [backward-incompatible] pkg/fbc/fbc.goTranslate() now returns validation failures for packages with invalid versions or timestamps that were previously silently accepted. The PR body documents this, but downstream callers relying on lenient behavior may be affected.

  • [breaking-schema] pkg/fbc/types.go — A nil *Date marshals as JSON null, whereas the previous behavior produced an empty string "". FilterIncompletePhases drops phases with nil dates before serialization in the default pipeline, but callers using custom filter pipelines could encounter null in date fields where strings were expected.

  • [stale-doc] docs/FBC_SCHEMA.md — Documents output field types as string for version names and dates. The JSON wire format is unchanged (custom marshalers produce strings), so the schema documentation is correct from a consumer perspective. However, noting the Go type change would help developers working with the package.

Low

  • [error-handling-gap] pkg/fbc/types.goParseMajorMinor discards strconv.ParseUint errors. The regex validates digit format but not magnitude; a version component exceeding 2^64-1 would silently corrupt to math.MaxUint64. Extremely unlikely in practice but technically unsound.

  • [scope-clarity] pkg/fbc/testdata/* — Reference test data reduced from 146 to 34 packages. The PR body explains this ("pipeline test bypasses PLCC validators, so the type layer now filters packages with formula timestamps, non-MAJOR.MINOR versions, etc."), but the explanation is easy to miss.

  • [sorting-conventions] pkg/fbc/fbc.gosort.Slice replaced with slices.SortFunc, while pkg/plcc/plcc.go still uses sort.Slice. Minor inconsistency during incremental modernization.

  • [stale-doc] docs/VALIDATION_RULES.md — References "empty" dates instead of "nil" in FilterIncompletePhases description and summary table.

  • [missing-doc] AGENTS.md — Key Types section does not list the new exported types MajorMinor and Date.

  • [stale-doc] AGENTS.md — Documents make test as go test -v ./... but the PR changes it to go test -v -count 1 ./....


Labels: PR introduces new structured types and changes exported API surface, qualifying as an enhancement.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the enhancement New feature or request label Jul 1, 2026
Replace plain string fields in the FBC output types with structured types
that enforce format invariants at construction time:

- Version.Name: string → MajorMinor (uint64 Major/Minor, regex-validated)
- Phase.StartDate/EndDate: string → *Date (wraps time.Time, YYYY-MM-DD)
- Platform.Versions: []string → []MajorMinor

Translation functions (translatePhase, translateVersion, newPackage) now
return errors instead of silently passing through malformed data. Errors
are collected with errors.Join (no fail-fast) and surfaced as validation
failures. Empty timestamps and "N/A" translate to nil *Date (not an error).

This enforces the FBC lifecycle contract — the schema that downstream
consumers depend on — which is separate from the PLCC validation policy
that governs data quality in the upstream source. PLCC validators check
whether product lifecycle data meets content policy (tier requirements,
date contiguity, release cadence, etc.); the FBC type layer ensures the
output schema is well-formed by construction regardless of which PLCC
validators are enabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@joelanford joelanford force-pushed the jl/structured-fbc-types branch from 5497c67 to 0056b34 Compare July 2, 2026 20:52
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:55 PM UTC · Completed 9:09 PM UTC
Commit: feb9bda · View workflow run →

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

Comment thread pkg/fbc/doc.go
// newPackage translates a PLCC product without failing on bad data (unparseable
// timestamps become empty strings). [Translate] then runs a configurable filter
// pipeline that cleans the output (e.g., dropping incomplete phases).
// The package follows a two-phase design: strict translation followed by output cleanup.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] architecture-shift-undocumented

The PR shifts the translation philosophy from lenient parsing to strict translation. While doc.go and AGENTS.md are updated, no formal design document captures the rationale, alternatives considered, or implications for the two-layer validation architecture.

Suggested fix: Consider documenting the design rationale more formally if the project adopts ADRs.

Comment thread pkg/fbc/fbc.go
// Version represents an operator version with its lifecycle phases and platform compatibility.
type Version struct {
Name string `json:"name"`
Name MajorMinor `json:"name"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] breaking-api-change

Exported type changes (Version.Name: string to MajorMinor, Phase.StartDate/EndDate: string to *Date, Platform.Versions: []string to []MajorMinor) are Go-level breaking changes. The module has no git tags or releases (pre-1.0), limiting external impact, but downstream Go importers would break at compile time.

Suggested fix: Acknowledge this as a pre-1.0 breaking change in the PR description.

Comment thread pkg/fbc/fbc.go

sort.Slice(pkg.Versions, func(i, j int) bool {
return compareMajorMinor(pkg.Versions[i].Name, pkg.Versions[j].Name) < 0
slices.SortFunc(pkg.Versions, func(a, b Version) int {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] breaking-json-output

Phase.StartDate and Phase.EndDate now use omitempty JSON tags. Nil dates are omitted from JSON output rather than appearing as empty strings. In practice, FilterIncompletePhases strips nil-date phases by default, so this mainly affects non-default filter pipelines.

Suggested fix: Remove omitempty from Phase date JSON tags to maintain backward compatibility, or document this as a known change.

Comment thread pkg/fbc/fbc.go
fv.Phases = append(fv.Phases, translatePhase(ph))
fp, err := translatePhase(ph)
if err != nil {
errs = append(errs, fmt.Errorf("phase %q: %w", ph.Name, err))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] breaking-behavior

Translate() now rejects products with invalid version names (non-MAJOR.MINOR) or unparseable timestamps, whereas previously these passed through with degraded data. This is intentional per the PR description and redundant with existing PLCC validators in the normal pipeline.

Suggested fix: Document this behavior change and its interaction with existing PLCC validators.

Comment thread Makefile
.PHONY: test
test:
go test -v ./...
go test -v -count 1 ./...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] scope-creep

Unrelated addition of -count 1 (disables test caching). Plausibly related to test data regeneration but not documented.

Comment thread pkg/fbc/fbc.go
errs = append(errs, fmt.Errorf("phase %q: %w", ph.Name, err))
} else {
phases = append(phases, fp)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] error-handling

Nested errors.Join in newPackage produces multi-line entries in ValidationResult.Reasons, which may render poorly in structured JSON logs.

Comment thread pkg/fbc/types.go
// Compare returns a negative value if m < other, zero if equal, positive if m > other.
func (m MajorMinor) Compare(other MajorMinor) int {
if m.Major != other.Major {
if m.Major < other.Major {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] panic-usage

ParseMajorMinor includes a panic for an unreachable code path. The codebase has no other panic calls in production code.

Comment thread pkg/fbc/types.go
}
minor, err := strconv.ParseUint(matches[2], 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid minor version %q: %w", matches[2], err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] naming-inconsistency

MajorMinor uses uint64 for Major/Minor fields; this is the default return type of strconv.ParseUint but oversized for version numbers.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant