Overview
The PLM DSL is the topmost layer of the lifecycle stack. It declares the things that customers, product managers and account executives care about: what is the product, what versions of it exist, what is on the roadmap, what stage of its lifecycle is it in, and when does each version reach end-of-life.
It is the only DSL in the lifecycle trio that is strictly build-time. Nothing in AcmeStore.Plm ships in any deployable artifact. Its source generator emits documentation and metadata files: a roadmap.md, a CHANGELOG.g.md, an ICS calendar of EOL dates, a JSON blob for the customer-facing version page. All consumed by tools, never by services.
The source generator produces:
- A
PlmRegistryof const strings (Plm.PRODUCT_*,Plm.RELEASE_*,Plm.MILESTONE_*) - A per-product
roadmap.mdaggregating[RoadmapItem]declarations and theRequirements.FEATURE_*they back CHANGELOG.g.mdper release, listing every feature shipped, every flag retired, every SLO that changed- A generated
lifecycle-state-machine.g.csper Product (Concept → Alpha → Beta → GA → Maintenance → EOL) - An
eol-calendar.icsfrom[DeprecationPolicy]declarations - Roslyn analyzers
PLM001-PLM100enforcing roadmap traceability and build-time-only isolation
Product
namespace Cmf.Plm.Lib;
/// <summary>
/// A customer-addressable product or component. Owns a set of ALM
/// services (which themselves ship SDLC build targets), passes
/// through lifecycle stages, and may declare a deprecation policy
/// that will eventually emit an EOL date.
/// </summary>
[MetaConcept("Product")]
[MetaConstraint("MustHaveAtLeastOneService",
"Services.Any()",
Message = "Product must own at least one ALM Service")]
public sealed class ProductAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("Services", "string[]", Required = true)]
public string[] Services { get; set; }
[MetaProperty("Description", "string")]
public string? Description { get; set; }
public ProductAttribute(string name, string[] services)
{
Name = name;
Services = services;
}
}namespace Cmf.Plm.Lib;
/// <summary>
/// A customer-addressable product or component. Owns a set of ALM
/// services (which themselves ship SDLC build targets), passes
/// through lifecycle stages, and may declare a deprecation policy
/// that will eventually emit an EOL date.
/// </summary>
[MetaConcept("Product")]
[MetaConstraint("MustHaveAtLeastOneService",
"Services.Any()",
Message = "Product must own at least one ALM Service")]
public sealed class ProductAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("Services", "string[]", Required = true)]
public string[] Services { get; set; }
[MetaProperty("Description", "string")]
public string? Description { get; set; }
public ProductAttribute(string name, string[] services)
{
Name = name;
Services = services;
}
}Vision
/// <summary>
/// A versioned strategic statement attached to a Product. Visions
/// are dated and append-only -- replacing one creates a new entry,
/// the old one survives in the changelog.
/// </summary>
[MetaConcept("Vision")]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class VisionAttribute : Attribute
{
[MetaProperty("Statement", "string", Required = true)]
public string Statement { get; }
[MetaProperty("AsOf", "DateOnly", Required = true)]
public string AsOf { get; set; }
public VisionAttribute(string statement, string asOf)
{
Statement = statement;
AsOf = asOf;
}
}/// <summary>
/// A versioned strategic statement attached to a Product. Visions
/// are dated and append-only -- replacing one creates a new entry,
/// the old one survives in the changelog.
/// </summary>
[MetaConcept("Vision")]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class VisionAttribute : Attribute
{
[MetaProperty("Statement", "string", Required = true)]
public string Statement { get; }
[MetaProperty("AsOf", "DateOnly", Required = true)]
public string AsOf { get; set; }
public VisionAttribute(string statement, string asOf)
{
Statement = statement;
AsOf = asOf;
}
}RoadmapItem
/// <summary>
/// A planned chunk of work. Must reference a Requirements DSL
/// feature -- the compiler refuses dangling roadmap items.
/// Optional Milestone groups items into customer-visible buckets.
/// </summary>
[MetaConcept("RoadmapItem")]
[MetaReference("Feature", "Requirement", Multiplicity = "1")]
[MetaConstraint("MustBackAFeature",
"Feature != null",
Message = "RoadmapItem must reference a [Feature] from the Requirements DSL")]
public sealed class RoadmapItemAttribute : Attribute
{
[MetaProperty("Feature", "string", Required = true)]
public string Feature { get; set; }
[MetaProperty("Milestone", "string")]
public string? Milestone { get; set; }
[MetaProperty("Status", "RoadmapStatus")]
public RoadmapStatus Status { get; set; } = RoadmapStatus.Planned;
public RoadmapItemAttribute(string feature) => Feature = feature;
}
public enum RoadmapStatus { Planned, InProgress, Shipped, Cancelled }/// <summary>
/// A planned chunk of work. Must reference a Requirements DSL
/// feature -- the compiler refuses dangling roadmap items.
/// Optional Milestone groups items into customer-visible buckets.
/// </summary>
[MetaConcept("RoadmapItem")]
[MetaReference("Feature", "Requirement", Multiplicity = "1")]
[MetaConstraint("MustBackAFeature",
"Feature != null",
Message = "RoadmapItem must reference a [Feature] from the Requirements DSL")]
public sealed class RoadmapItemAttribute : Attribute
{
[MetaProperty("Feature", "string", Required = true)]
public string Feature { get; set; }
[MetaProperty("Milestone", "string")]
public string? Milestone { get; set; }
[MetaProperty("Status", "RoadmapStatus")]
public RoadmapStatus Status { get; set; } = RoadmapStatus.Planned;
public RoadmapItemAttribute(string feature) => Feature = feature;
}
public enum RoadmapStatus { Planned, InProgress, Shipped, Cancelled }Release
/// <summary>
/// A semver-versioned release of a Product. Pipeline references
/// the SDLC pipeline that cuts it; Channel references the SDLC
/// release channel it ships to. The compiler verifies that the
/// channel is allowed by the active branch policy.
/// </summary>
[MetaConcept("Release")]
[MetaReference("Pipeline", "Pipeline", Multiplicity = "1")]
[MetaConstraint("VersionMustBeSemver",
"Regex.IsMatch(Version, '^\\d+\\.\\d+\\.\\d+(-[A-Za-z0-9.-]+)?$')",
Message = "Release Version must be valid semver")]
public sealed class ReleaseAttribute : Attribute
{
[MetaProperty("Product", "string", Required = true)]
public string Product { get; }
[MetaProperty("Version", "string", Required = true)]
public string Version { get; set; }
[MetaProperty("Date", "DateOnly", Required = true)]
public string Date { get; set; }
[MetaProperty("Pipeline", "string", Required = true)]
public string Pipeline { get; set; }
[MetaProperty("Channel", "ReleaseChannel", Required = true)]
public ReleaseChannel Channel { get; set; }
public ReleaseAttribute(string product, string version, string date,
string pipeline, ReleaseChannel channel)
{
Product = product;
Version = version;
Date = date;
Pipeline = pipeline;
Channel = channel;
}
}/// <summary>
/// A semver-versioned release of a Product. Pipeline references
/// the SDLC pipeline that cuts it; Channel references the SDLC
/// release channel it ships to. The compiler verifies that the
/// channel is allowed by the active branch policy.
/// </summary>
[MetaConcept("Release")]
[MetaReference("Pipeline", "Pipeline", Multiplicity = "1")]
[MetaConstraint("VersionMustBeSemver",
"Regex.IsMatch(Version, '^\\d+\\.\\d+\\.\\d+(-[A-Za-z0-9.-]+)?$')",
Message = "Release Version must be valid semver")]
public sealed class ReleaseAttribute : Attribute
{
[MetaProperty("Product", "string", Required = true)]
public string Product { get; }
[MetaProperty("Version", "string", Required = true)]
public string Version { get; set; }
[MetaProperty("Date", "DateOnly", Required = true)]
public string Date { get; set; }
[MetaProperty("Pipeline", "string", Required = true)]
public string Pipeline { get; set; }
[MetaProperty("Channel", "ReleaseChannel", Required = true)]
public ReleaseChannel Channel { get; set; }
public ReleaseAttribute(string product, string version, string date,
string pipeline, ReleaseChannel channel)
{
Product = product;
Version = version;
Date = date;
Pipeline = pipeline;
Channel = channel;
}
}Milestone
/// <summary>
/// A dated bucket that aggregates RoadmapItems and Releases. The
/// generator emits one section per milestone in the per-product
/// roadmap.md.
/// </summary>
[MetaConcept("Milestone")]
public sealed class MilestoneAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("TargetDate", "DateOnly", Required = true)]
public string TargetDate { get; set; }
public MilestoneAttribute(string name, string targetDate)
{
Name = name;
TargetDate = targetDate;
}
}/// <summary>
/// A dated bucket that aggregates RoadmapItems and Releases. The
/// generator emits one section per milestone in the per-product
/// roadmap.md.
/// </summary>
[MetaConcept("Milestone")]
public sealed class MilestoneAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("TargetDate", "DateOnly", Required = true)]
public string TargetDate { get; set; }
public MilestoneAttribute(string name, string targetDate)
{
Name = name;
TargetDate = targetDate;
}
}LifecycleStage
/// <summary>
/// Where the Product currently sits on its lifecycle curve. The
/// compiler enforces forward-only transitions: Concept -> Alpha -> Beta
/// -> GA -> Maintenance -> EOL. Going backwards is a [Deprecation],
/// not a [LifecycleStage].
/// </summary>
[MetaConcept("LifecycleStage")]
[MetaConstraint("StageOrderingIsMonotonic",
"this.Stage >= product.PreviousStage",
Message = "LifecycleStage cannot go backwards")]
public sealed class LifecycleStageAttribute : Attribute
{
[MetaProperty("Stage", "Stage", Required = true)]
public Stage Stage { get; set; }
public LifecycleStageAttribute(Stage stage) => Stage = stage;
}
public enum Stage { Concept, Alpha, Beta, GA, Maintenance, EOL }/// <summary>
/// Where the Product currently sits on its lifecycle curve. The
/// compiler enforces forward-only transitions: Concept -> Alpha -> Beta
/// -> GA -> Maintenance -> EOL. Going backwards is a [Deprecation],
/// not a [LifecycleStage].
/// </summary>
[MetaConcept("LifecycleStage")]
[MetaConstraint("StageOrderingIsMonotonic",
"this.Stage >= product.PreviousStage",
Message = "LifecycleStage cannot go backwards")]
public sealed class LifecycleStageAttribute : Attribute
{
[MetaProperty("Stage", "Stage", Required = true)]
public Stage Stage { get; set; }
public LifecycleStageAttribute(Stage stage) => Stage = stage;
}
public enum Stage { Concept, Alpha, Beta, GA, Maintenance, EOL }DeprecationPolicy
/// <summary>
/// Declares the notice period customers receive before a Product
/// hits EOL. The generator computes the EOL date and emits it into
/// the eol-calendar.ics so that downstream tooling can publish
/// reminders.
/// </summary>
[MetaConcept("DeprecationPolicy")]
[MetaInherits("MetaConstraint")]
public sealed class DeprecationPolicyAttribute : Attribute
{
[MetaProperty("NoticePeriodDays", "int", Required = true)]
public int NoticePeriodDays { get; set; }
[MetaProperty("PolicyUrl", "string")]
public string? PolicyUrl { get; set; }
public DeprecationPolicyAttribute(int noticePeriodDays)
=> NoticePeriodDays = noticePeriodDays;
}/// <summary>
/// Declares the notice period customers receive before a Product
/// hits EOL. The generator computes the EOL date and emits it into
/// the eol-calendar.ics so that downstream tooling can publish
/// reminders.
/// </summary>
[MetaConcept("DeprecationPolicy")]
[MetaInherits("MetaConstraint")]
public sealed class DeprecationPolicyAttribute : Attribute
{
[MetaProperty("NoticePeriodDays", "int", Required = true)]
public int NoticePeriodDays { get; set; }
[MetaProperty("PolicyUrl", "string")]
public string? PolicyUrl { get; set; }
public DeprecationPolicyAttribute(int noticePeriodDays)
=> NoticePeriodDays = noticePeriodDays;
}What the developer writes
namespace AcmeStore.Plm;
[Product("Store",
Services = new[] { Alm.SERVICE_API, Alm.SERVICE_WORKER, Alm.SERVICE_WEB },
Description = "Public e-commerce storefront")]
[Vision("Become the easiest checkout experience on the web", AsOf = "2026-01-15")]
[LifecycleStage(Stage.GA)]
[DeprecationPolicy(NoticePeriodDays = 180,
PolicyUrl = "https://acmestore.example/legal/deprecation")]
public partial class StoreProduct { }
// ── Milestones ──
[Milestone("Q2-2026", TargetDate = "2026-06-30")]
public partial class Q2_2026_Milestone { }
[Milestone("Q3-2026", TargetDate = "2026-09-30")]
public partial class Q3_2026_Milestone { }
// ── Roadmap ──
[RoadmapItem(Requirements.FEATURE_USER_ROLES,
Milestone = "Q2-2026", Status = RoadmapStatus.Shipped)]
public partial class RM_UserRoles { }
[RoadmapItem(Requirements.FEATURE_NEW_CHECKOUT,
Milestone = "Q2-2026", Status = RoadmapStatus.InProgress)]
public partial class RM_NewCheckout { }
[RoadmapItem(Requirements.FEATURE_LOYALTY_PROGRAM,
Milestone = "Q3-2026", Status = RoadmapStatus.Planned)]
public partial class RM_Loyalty { }
// ── Releases ──
[Release("Store",
Version = "2.3.0",
Date = "2026-03-15",
Pipeline = Sdlc.PIPELINE_MAIN,
Channel = ReleaseChannel.Stable)]
public partial class Store_2_3_0 { }
[Release("Store",
Version = "2.4.0-preview.1",
Date = "2026-04-01",
Pipeline = Sdlc.PIPELINE_MAIN,
Channel = ReleaseChannel.Preview)]
public partial class Store_2_4_0_preview { }namespace AcmeStore.Plm;
[Product("Store",
Services = new[] { Alm.SERVICE_API, Alm.SERVICE_WORKER, Alm.SERVICE_WEB },
Description = "Public e-commerce storefront")]
[Vision("Become the easiest checkout experience on the web", AsOf = "2026-01-15")]
[LifecycleStage(Stage.GA)]
[DeprecationPolicy(NoticePeriodDays = 180,
PolicyUrl = "https://acmestore.example/legal/deprecation")]
public partial class StoreProduct { }
// ── Milestones ──
[Milestone("Q2-2026", TargetDate = "2026-06-30")]
public partial class Q2_2026_Milestone { }
[Milestone("Q3-2026", TargetDate = "2026-09-30")]
public partial class Q3_2026_Milestone { }
// ── Roadmap ──
[RoadmapItem(Requirements.FEATURE_USER_ROLES,
Milestone = "Q2-2026", Status = RoadmapStatus.Shipped)]
public partial class RM_UserRoles { }
[RoadmapItem(Requirements.FEATURE_NEW_CHECKOUT,
Milestone = "Q2-2026", Status = RoadmapStatus.InProgress)]
public partial class RM_NewCheckout { }
[RoadmapItem(Requirements.FEATURE_LOYALTY_PROGRAM,
Milestone = "Q3-2026", Status = RoadmapStatus.Planned)]
public partial class RM_Loyalty { }
// ── Releases ──
[Release("Store",
Version = "2.3.0",
Date = "2026-03-15",
Pipeline = Sdlc.PIPELINE_MAIN,
Channel = ReleaseChannel.Stable)]
public partial class Store_2_3_0 { }
[Release("Store",
Version = "2.4.0-preview.1",
Date = "2026-04-01",
Pipeline = Sdlc.PIPELINE_MAIN,
Channel = ReleaseChannel.Preview)]
public partial class Store_2_4_0_preview { }Two cross-DSL guarantees the compiler enforces here:
Alm.SERVICE_APIexists and points at a valid[Service]. Removing the service inAcmeStore.AlmbreaksStoreProductat compile time.Requirements.FEATURE_NEW_CHECKOUTexists and points at a valid[Feature]. Removing the feature inAcmeStore.Requirementsbreaks the roadmap item at compile time. Roadmaps cannot lie.
1. PlmRegistry
// Generated: PlmRegistry.g.cs
namespace AcmeStore.Plm;
public static class Plm
{
public const string PRODUCT_STORE = "AcmeStore.Plm.StoreProduct";
public const string RELEASE_STORE_2_3_0 = "Store::2.3.0";
public const string RELEASE_STORE_2_4_0_PREVIEW = "Store::2.4.0-preview.1";
public const string MILESTONE_Q2_2026 = "Q2-2026";
public const string MILESTONE_Q3_2026 = "Q3-2026";
}// Generated: PlmRegistry.g.cs
namespace AcmeStore.Plm;
public static class Plm
{
public const string PRODUCT_STORE = "AcmeStore.Plm.StoreProduct";
public const string RELEASE_STORE_2_3_0 = "Store::2.3.0";
public const string RELEASE_STORE_2_4_0_PREVIEW = "Store::2.4.0-preview.1";
public const string MILESTONE_Q2_2026 = "Q2-2026";
public const string MILESTONE_Q3_2026 = "Q3-2026";
}2. roadmap.md (per product)
<!-- Generated: docs/products/store/roadmap.md -->
# Store -- Roadmap
> Become the easiest checkout experience on the web (vision as of 2026-01-15)
## Q2-2026 (target: 2026-06-30)
| Status | Feature | Requirement ID |
|-------------|--------------------------------------|-------------------|
| ✓ Shipped | User roles & permissions | FEATURE_USER_ROLES |
| ◐ In progress | New checkout flow | FEATURE_NEW_CHECKOUT |
## Q3-2026 (target: 2026-09-30)
| Status | Feature | Requirement ID |
|-------------|----------------------|------------------------|
| · Planned | Loyalty program | FEATURE_LOYALTY_PROGRAM |<!-- Generated: docs/products/store/roadmap.md -->
# Store -- Roadmap
> Become the easiest checkout experience on the web (vision as of 2026-01-15)
## Q2-2026 (target: 2026-06-30)
| Status | Feature | Requirement ID |
|-------------|--------------------------------------|-------------------|
| ✓ Shipped | User roles & permissions | FEATURE_USER_ROLES |
| ◐ In progress | New checkout flow | FEATURE_NEW_CHECKOUT |
## Q3-2026 (target: 2026-09-30)
| Status | Feature | Requirement ID |
|-------------|----------------------|------------------------|
| · Planned | Loyalty program | FEATURE_LOYALTY_PROGRAM |3. CHANGELOG.g.md (per release)
<!-- Generated: docs/products/store/CHANGELOG.md -->
# Store CHANGELOG
## 2.4.0-preview.1 -- 2026-04-01 (Preview)
Pipeline: `MainBuild`
### In progress
- New checkout flow (FEATURE_NEW_CHECKOUT)
- Behind feature flag `NewCheckout` (canary 10%)
## 2.3.0 -- 2026-03-15 (Stable)
Pipeline: `MainBuild`
### Shipped features
- User roles & permissions (FEATURE_USER_ROLES)
### Retired flags
- `LegacyAuth` -- removed after migration complete<!-- Generated: docs/products/store/CHANGELOG.md -->
# Store CHANGELOG
## 2.4.0-preview.1 -- 2026-04-01 (Preview)
Pipeline: `MainBuild`
### In progress
- New checkout flow (FEATURE_NEW_CHECKOUT)
- Behind feature flag `NewCheckout` (canary 10%)
## 2.3.0 -- 2026-03-15 (Stable)
Pipeline: `MainBuild`
### Shipped features
- User roles & permissions (FEATURE_USER_ROLES)
### Retired flags
- `LegacyAuth` -- removed after migration complete4. Lifecycle state machine
// Generated: StoreProductLifecycle.g.cs
public enum StoreProductLifecycleStage
{
Concept, Alpha, Beta, GA, Maintenance, EOL
}
public static class StoreProductLifecycle
{
public static StoreProductLifecycleStage Current
=> StoreProductLifecycleStage.GA;
private static readonly (StoreProductLifecycleStage From, StoreProductLifecycleStage To)[] Transitions =
{
(StoreProductLifecycleStage.Concept, StoreProductLifecycleStage.Alpha),
(StoreProductLifecycleStage.Alpha, StoreProductLifecycleStage.Beta),
(StoreProductLifecycleStage.Beta, StoreProductLifecycleStage.GA),
(StoreProductLifecycleStage.GA, StoreProductLifecycleStage.Maintenance),
(StoreProductLifecycleStage.Maintenance, StoreProductLifecycleStage.EOL),
};
public static bool IsValidTransition(
StoreProductLifecycleStage from, StoreProductLifecycleStage to)
=> Transitions.Any(t => t.From == from && t.To == to);
}// Generated: StoreProductLifecycle.g.cs
public enum StoreProductLifecycleStage
{
Concept, Alpha, Beta, GA, Maintenance, EOL
}
public static class StoreProductLifecycle
{
public static StoreProductLifecycleStage Current
=> StoreProductLifecycleStage.GA;
private static readonly (StoreProductLifecycleStage From, StoreProductLifecycleStage To)[] Transitions =
{
(StoreProductLifecycleStage.Concept, StoreProductLifecycleStage.Alpha),
(StoreProductLifecycleStage.Alpha, StoreProductLifecycleStage.Beta),
(StoreProductLifecycleStage.Beta, StoreProductLifecycleStage.GA),
(StoreProductLifecycleStage.GA, StoreProductLifecycleStage.Maintenance),
(StoreProductLifecycleStage.Maintenance, StoreProductLifecycleStage.EOL),
};
public static bool IsValidTransition(
StoreProductLifecycleStage from, StoreProductLifecycleStage to)
=> Transitions.Any(t => t.From == from && t.To == to);
}5. EOL calendar
BEGIN:VCALENDAR
PRODID:-//AcmeStore//PLM DSL//EN
VERSION:2.0
BEGIN:VEVENT
UID:store-eol@acmestore
SUMMARY:Store -- EOL notice window opens
DTSTART;VALUE=DATE:20261001
DTEND;VALUE=DATE:20261001
DESCRIPTION:NoticePeriod=180 days. Policy: https://acmestore.example/legal/deprecation
END:VEVENT
END:VCALENDARBEGIN:VCALENDAR
PRODID:-//AcmeStore//PLM DSL//EN
VERSION:2.0
BEGIN:VEVENT
UID:store-eol@acmestore
SUMMARY:Store -- EOL notice window opens
DTSTART;VALUE=DATE:20261001
DTEND;VALUE=DATE:20261001
DESCRIPTION:NoticePeriod=180 days. Policy: https://acmestore.example/legal/deprecation
END:VEVENT
END:VCALENDARThe EOL date is computed from LifecycleStage.EOL-bearing releases plus DeprecationPolicy.NoticePeriodDays. As long as the product is in GA or Maintenance, the calendar event slides forward automatically; cutting an explicit [LifecycleStage(Stage.EOL)] release pins it.
6. Generation pipeline
Cross-DSL references
PLM is the apex of the lifecycle stack and references everything below it:
[Product(Services = ...)]→ ALMAlm.SERVICE_*[RoadmapItem(Feature = ...)]→ Requirements DSLRequirements.FEATURE_*[Release(Pipeline = ...)]→ SDLCSdlc.PIPELINE_*[Release(Channel = ...)]→ SDLCReleaseChannelenum
Reverse references are forbidden. SDLC, ALM, Requirements and the domain do not reference PLM. The compiler enforces this with analyzer PLM100.
Analyzers, including the build-time-only guard
| ID | Severity | Message |
|---|---|---|
PLM001 |
Error | RoadmapItem {R} references unknown Feature {F} |
PLM002 |
Error | Release {R} declares GA without a prior Beta release |
PLM003 |
Error | Release {R} Channel {C} is not allowed by active branch policy |
PLM004 |
Warning | Product {P} has Vision older than 12 months -- consider a refresh |
PLM005 |
Error | LifecycleStage transition violates monotonic ordering |
PLM100 |
Error | Project {X} (any non-tooling layer) must not reference AcmeStore.Plm. PLM is build-time only. Use the generated artifacts (roadmap.md, CHANGELOG.g.md) instead |
PLM100 is even stricter than its ALM cousin: it forbids any runtime project from referencing the PLM assembly, including the AppHost. The only legal consumers are the PLM tooling CLI and other PLM projects. This is what allows PLM declarations to carry forward-looking, customer-sensitive information (vision statements, internal roadmap items) without that information ever ending up in a deployed binary that a curious user could decompile.
Why a separate DSL instead of PLM-as-comments
The naive approach is to put product information in /// doc comments and harvest them with a script. Three problems with that approach:
- No type safety. Comments cannot reference
Sdlc.PIPELINE_MAIN. They reference free text. They drift. - No constraint enforcement. Comments cannot fail the build when a release ships GA without a prior Beta. They are inert.
- Mixed concerns. Putting customer-facing roadmap items in source comments leaks them into generated documentation pages by accident.
The PLM DSL gives PMs and product owners a first-class surface in the codebase that is checked by the same compiler that checks the production code, while remaining strictly out of the runtime dependency graph. The next chapter (Part XVIII -- AcmeStore Walkthrough) shows the three lifecycle DSLs working together end-to-end with a full dependency-inversion diagram and feature-flag walkthrough.