loginowskid commited on
Commit
7e13d74
·
1 Parent(s): 2409b81

Structural PKG.* checks @919af26

Browse files
tools/spec_sync/state.json CHANGED
@@ -6,6 +6,10 @@
6
  "synced_at": "2026-06-02T00:00:00Z",
7
  "synced_by": "initial-setup",
8
  "rules_pinned_against": [
9
- "AA.002 supported-file-types (validate.py:_AA_002_ALLOWED)"
 
 
 
 
10
  ]
11
  }
 
6
  "synced_at": "2026-06-02T00:00:00Z",
7
  "synced_by": "initial-setup",
8
  "rules_pinned_against": [
9
+ "AA.002 supported-file-types (validate.py:_AA_002_ALLOWED) — foundation",
10
+ "PKG.1 interface file (validate.py:_check_pkg_layout) — local SDK packaging spec",
11
+ "PKG.6 package manifest (validate.py:_check_pkg_layout) — local SDK packaging spec",
12
+ "PKG.NO-ROOT-USD no root USDs (validate.py:_check_pkg_layout) — local SDK packaging spec",
13
+ "PKG.NO-ARCHIVES no zip files (validate.py:_check_pkg_layout) — local SDK packaging spec"
14
  ]
15
  }
tools/validation/plugins/simready-report/skills/simready-report/validate.py CHANGED
@@ -692,21 +692,33 @@ _ATOMIC_ASSET_PATHS = (f"{_FOUNDATION_SPECS_BASE}/core/atomic_asset/"
692
 
693
 
694
  def run_preliminary_checks(root: Path) -> list[dict]:
695
- """Preliminary check phase: filesystem-only foundation requirements,
696
- evaluated before USD traversal.
697
-
698
- These checks are intentionally cheap and deterministic no LLM
699
- calls in the validation hot path. Each rule is a small function
700
- with a strong link to its foundation spec section. Drift detection
701
- (does our hardcoded list still match the spec?) runs OUT of band
702
- via tools/spec_sync/ (a weekly job that uses the agent to compare
703
- the foundation spec text to these hardcoded rules and opens a PR
704
- on drift).
705
-
706
- Currently implemented:
707
- - AA.002 supported-file-types
 
 
 
 
 
 
 
 
 
708
  """
709
- return _check_aa_002_supported_file_types(root)
 
 
 
710
 
711
 
712
  # AA.002 supported-file-types — hardcoded from the foundation spec at
@@ -777,6 +789,128 @@ def _check_aa_002_supported_file_types(root: Path) -> list[dict]:
777
  return issues
778
 
779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
780
  def discover_assets(
781
  root: Path, exclude: Path | None = None,
782
  ) -> tuple[list[Path], list[dict]]:
 
692
 
693
 
694
  def run_preliminary_checks(root: Path) -> list[dict]:
695
+ """Preliminary check phase: filesystem-only checks, evaluated
696
+ before USD traversal.
697
+
698
+ Cheap, deterministic, runs every validation. The point is to
699
+ surface ALL filesystem-visible spec violations in a single
700
+ roundtrip partners fix everything at once instead of getting
701
+ piece-meal feedback over multiple iterations.
702
+
703
+ Each check has a strong link to its source-of-truth spec section.
704
+ Two sources are in play:
705
+ - Foundation specs (NVIDIA/simready-foundation): AA.002.
706
+ Drift-watched via tools/spec_sync/.
707
+ - SDK packaging spec (docs/sdk/packaging-spec.md): PKG.*.
708
+ Lives in this repo, no drift watch needed.
709
+
710
+ Implemented:
711
+ - AA.002 supported-file-types (foundation)
712
+ - PKG.1 interface file convention (SDK §"Discovery Rules" #1+#4)
713
+ - PKG.6 package manifest required (SDK §"Discovery Rules" #6)
714
+ - PKG.NO-ROOT-USD no USDs at dataset root (SDK §"Folder Structure")
715
+ - PKG.NO-ARCHIVES no zip files (SDK §"Folder Structure" — packages
716
+ ship unpacked)
717
  """
718
+ issues: list[dict] = []
719
+ issues.extend(_check_aa_002_supported_file_types(root))
720
+ issues.extend(_check_pkg_layout(root))
721
+ return issues
722
 
723
 
724
  # AA.002 supported-file-types — hardcoded from the foundation spec at
 
789
  return issues
790
 
791
 
792
+ # SDK packaging spec — the SimReady asset packaging convention this
793
+ # repo's docs/sdk/packaging-spec.md mandates. Independent from the
794
+ # foundation specs (which cover USD-content, not packaging structure).
795
+ _SDK_PACKAGING_SPEC_URL = ("https://github.com/NVIDIA-dev/simready-oem-library-pm/"
796
+ "blob/main/docs/sdk/packaging-spec.md")
797
+ _SDK_PACKAGING_SPEC_FOLDER = f"{_SDK_PACKAGING_SPEC_URL}#folder-structure"
798
+ _SDK_PACKAGING_SPEC_DISCOVERY = f"{_SDK_PACKAGING_SPEC_URL}#discovery-rules"
799
+ _SDK_PACKAGING_SPEC_MANIFEST = f"{_SDK_PACKAGING_SPEC_URL}#package-manifest-wrapp"
800
+
801
+
802
+ def _check_pkg_layout(root: Path) -> list[dict]:
803
+ """Checks against the SDK packaging spec's structural requirements.
804
+
805
+ Spec: docs/sdk/packaging-spec.md (in this repo). Codes follow the
806
+ spec's own numbered Discovery Rules where applicable.
807
+
808
+ Rules enforced:
809
+ PKG.1 Each top-level dir must contain
810
+ <dirname>/<dirname>.usd (Discovery Rules #1+#4).
811
+ PKG.6 Each top-level dir must contain .<dirname>.wrapp
812
+ (Discovery Rules #6 — "required, flag as error").
813
+ PKG.NO-ROOT-USD No USD files at the dataset root — each asset
814
+ must live in its own directory per Folder
815
+ Structure.
816
+ PKG.NO-ARCHIVES No .zip files anywhere — SimReady packages ship
817
+ unpacked per Folder Structure.
818
+ """
819
+ issues: list[dict] = []
820
+ try:
821
+ entries = sorted(root.iterdir())
822
+ except OSError as e:
823
+ return [{
824
+ "code": "PKG.READ-FAILED",
825
+ "severity": "failure",
826
+ "path": str(root),
827
+ "spec_url": _SDK_PACKAGING_SPEC_URL,
828
+ "msg": f"Could not read dataset root: {e}",
829
+ }]
830
+
831
+ has_any_bundle_dir = False
832
+ for entry in entries:
833
+ rel = entry.name
834
+ if entry.is_file():
835
+ suffix = entry.suffix.lower()
836
+ if suffix == ".zip":
837
+ issues.append({
838
+ "code": "PKG.NO-ARCHIVES",
839
+ "severity": "failure",
840
+ "path": rel,
841
+ "spec_url": _SDK_PACKAGING_SPEC_FOLDER,
842
+ "msg": (f"'{rel}' is a zip archive. SimReady packages "
843
+ f"must be delivered as unpacked directories — "
844
+ f"extract all archives and re-publish."),
845
+ })
846
+ elif suffix in {".usd", ".usda", ".usdc", ".usdz"}:
847
+ issues.append({
848
+ "code": "PKG.NO-ROOT-USD",
849
+ "severity": "failure",
850
+ "path": rel,
851
+ "spec_url": _SDK_PACKAGING_SPEC_FOLDER,
852
+ "msg": (f"'{rel}' is a USD file at the dataset root. "
853
+ f"Each asset must live in its own directory: "
854
+ f"<asset_name>/<asset_name>.usd."),
855
+ })
856
+ continue
857
+ if not entry.is_dir():
858
+ continue
859
+ if entry.name.startswith(".") or entry.name in _SKIP_DIR_NAMES:
860
+ continue
861
+ has_any_bundle_dir = True
862
+
863
+ # PKG.1 — interface file convention
864
+ interface_candidates = [
865
+ entry / f"{entry.name}.usd",
866
+ entry / f"{entry.name}.usda",
867
+ entry / f"{entry.name}.usdc",
868
+ ]
869
+ if not any(p.is_file() for p in interface_candidates):
870
+ try:
871
+ found = [str(p.relative_to(entry))
872
+ for p in sorted(entry.rglob("*"))
873
+ if p.is_file() and p.suffix.lower() in USD_EXTS][:5]
874
+ except OSError:
875
+ found = []
876
+ hint = (f" Found USDs here: {', '.join(found)}"
877
+ if found else " No USD files found in this directory.")
878
+ issues.append({
879
+ "code": "PKG.1",
880
+ "severity": "failure",
881
+ "path": rel + "/",
882
+ "spec_url": _SDK_PACKAGING_SPEC_DISCOVERY,
883
+ "msg": (f"Directory '{rel}/' must contain an interface "
884
+ f"file named '{rel}.usd' (or .usda/.usdc) per "
885
+ f"the SimReady packaging spec.{hint}"),
886
+ })
887
+
888
+ # PKG.6 — package manifest
889
+ manifest = entry / f".{entry.name}.wrapp"
890
+ if not manifest.is_file():
891
+ issues.append({
892
+ "code": "PKG.6",
893
+ "severity": "failure",
894
+ "path": rel + "/",
895
+ "spec_url": _SDK_PACKAGING_SPEC_MANIFEST,
896
+ "msg": (f"Directory '{rel}/' is missing the required "
897
+ f"package manifest '.{rel}.wrapp'."),
898
+ })
899
+
900
+ if not has_any_bundle_dir and not issues:
901
+ issues.append({
902
+ "code": "PKG.EMPTY",
903
+ "severity": "failure",
904
+ "path": ".",
905
+ "spec_url": _SDK_PACKAGING_SPEC_FOLDER,
906
+ "msg": (f"Dataset root contains no asset directories. Per "
907
+ f"the SimReady packaging spec, each asset lives in "
908
+ f"its own top-level directory."),
909
+ })
910
+
911
+ return issues
912
+
913
+
914
  def discover_assets(
915
  root: Path, exclude: Path | None = None,
916
  ) -> tuple[list[Path], list[dict]]: